From 10c5b24be0ac7841920761ca9f1bee42a25ca099 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <987Nabil@users.noreply.github.com> Date: Tue, 1 Jun 2021 00:33:30 +0200 Subject: [PATCH 01/23] Fix typo --- src/main/scala/sbtghactions/GenerativeKeys.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/sbtghactions/GenerativeKeys.scala b/src/main/scala/sbtghactions/GenerativeKeys.scala index a07c9c8..cbed059 100644 --- a/src/main/scala/sbtghactions/GenerativeKeys.scala +++ b/src/main/scala/sbtghactions/GenerativeKeys.scala @@ -43,7 +43,7 @@ trait GenerativeKeys { lazy val githubWorkflowPublishPreamble = settingKey[Seq[WorkflowStep]]("A list of steps to insert after base setup but before publishing (default: [])") lazy val githubWorkflowPublishPostamble = settingKey[Seq[WorkflowStep]]("A list of steps to insert after publication but before the end of the publish job (default: [])") - lazy val githubWorkflowPublish = settingKey[Seq[WorkflowStep]]("A sequence workflow steps which publishe the project (default: [Sbt(List(\"+publish\"))])") + lazy val githubWorkflowPublish = settingKey[Seq[WorkflowStep]]("A sequence workflow steps which publishes the project (default: [Sbt(List(\"+publish\"))])") lazy val githubWorkflowPublishTargetBranches = settingKey[Seq[RefPredicate]]("A set of branch predicates which will be applied to determine whether the current branch gets a publication stage; if empty, publish will be skipped entirely (default: [== main])") lazy val githubWorkflowPublishCond = settingKey[Option[String]]("A set of conditionals to apply to the publish job to further restrict its run (default: [])") From b27937b89c1f1ee9f4cbec775f9c3f868d74acf3 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <987Nabil@users.noreply.github.com> Date: Tue, 1 Jun 2021 09:54:02 +0200 Subject: [PATCH 02/23] Use simple generic workflow for internal workflow compilation --- .../scala/sbtghactions/GenerativePlugin.scala | 38 ++--- src/main/scala/sbtghactions/Workflow.scala | 124 ++++++++++++++++ .../sbtghactions/GenerativePluginSpec.scala | 139 +++++++++++------- 3 files changed, 218 insertions(+), 83 deletions(-) create mode 100644 src/main/scala/sbtghactions/Workflow.scala diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index bfbdb10..d925f55 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -395,12 +395,8 @@ ${indent(job.steps.map(compileStep(_, sbt, declareShell = declareShell)).mkStrin } def compileWorkflow( - name: String, - branches: List[String], - tags: List[String], - prEventTypes: List[PREventType], + workflow: Workflow, env: Map[String, String], - jobs: List[WorkflowJob], sbt: String) : String = { @@ -410,17 +406,6 @@ ${indent(job.steps.map(compileStep(_, sbt, declareShell = declareShell)).mkStrin else renderedEnvPre + "\n\n" - val renderedTypesPre = prEventTypes.map(compilePREventType).mkString("[", ", ", "]") - val renderedTypes = if (prEventTypes.sortBy(_.toString) == PREventType.Defaults) - "" - else - "\n" + indent("types: " + renderedTypesPre, 2) - - val renderedTags = if (tags.isEmpty) - "" - else -s""" - tags: [${tags.map(wrap).mkString(", ")}]""" s"""# This file was automatically generated by sbt-github-actions using the # githubWorkflowGenerate task. You should add and commit this file to @@ -429,16 +414,10 @@ s""" # change your sbt build configuration to revise the workflow description # to meet your needs, then regenerate this file. -name: ${wrap(name)} - -on: - pull_request: - branches: [${branches.map(wrap).mkString(", ")}]$renderedTypes - push: - branches: [${branches.map(wrap).mkString(", ")}]$renderedTags +${workflow.render} ${renderedEnv}jobs: -${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}""" +${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}""" } val settingDefaults = Seq( @@ -664,12 +643,15 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}""" githubWorkflowSbtCommand.value } compileWorkflow( + Workflow( "Continuous Integration", - githubWorkflowTargetBranches.value.toList, - githubWorkflowTargetTags.value.toList, - githubWorkflowPREventTypes.value.toList, - githubWorkflowEnv.value, + List( + Push(githubWorkflowTargetBranches.value.toList,githubWorkflowTargetTags.value.toList), + PullRequest(githubWorkflowTargetBranches.value.toList,githubWorkflowPREventTypes.value.toList ) + ), githubWorkflowGeneratedCI.value.toList, + ), + githubWorkflowEnv.value, sbt) } diff --git a/src/main/scala/sbtghactions/Workflow.scala b/src/main/scala/sbtghactions/Workflow.scala new file mode 100644 index 0000000..e230d0a --- /dev/null +++ b/src/main/scala/sbtghactions/Workflow.scala @@ -0,0 +1,124 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +import sbtghactions.Help._ + +object Help { + + def wrap(str: String): String = + if (str.indexOf('\n') >= 0) + "|\n" + indent(str, 1) + else if (isSafeString(str)) + str + else + s"'${str.replace("'", "''")}'" + + private def isSafeString(str: String): Boolean = + !(str.indexOf(':') >= 0 || // pretend colon is illegal everywhere for simplicity + str.indexOf('#') >= 0 || // same for comment + str.indexOf('!') == 0 || + str.indexOf('*') == 0 || + str.indexOf('-') == 0 || + str.indexOf('?') == 0 || + str.indexOf('{') == 0 || + str.indexOf('}') == 0 || + str.indexOf('[') == 0 || + str.indexOf(']') == 0 || + str.indexOf(',') == 0 || + str.indexOf('|') == 0 || + str.indexOf('>') == 0 || + str.indexOf('@') == 0 || + str.indexOf('`') == 0 || + str.indexOf('"') == 0 || + str.indexOf('\'') == 0 || + str.indexOf('&') == 0) + + def indentOnce(output: String): String = indent(output, 1) + + def indent(output: String, level: Int): String = { + val space = (0 until level * 2).map(_ => ' ').mkString + (space + output.replace("\n", s"\n$space")).replaceAll("""\n[ ]+\n""", "\n\n") + } + + def renderParamWithList(paramName: String, items: Seq[String]): String = { + val rendered = items.map(wrap) + if (rendered.map(_.length).sum < 40) // just arbitrarily... + rendered.mkString(s"$paramName: [", ", ", "]") + else + rendered.map("- " + _).map(indentOnce).mkString(s"$paramName:\n","\n","\n") + } + + object SnakeCase { + private val re = "[A-Z]+".r + + def apply(property: String): String = + re.replaceAllIn(property.head.toLower +: property.tail, { m => s"_${m.matched.toLowerCase}" }) + } + +} + +sealed trait Event { + def render: String +} + +final case class Schedule(cron: String) extends Event { + + override def render: String = + s""" + |schedule: + | - cron: '$cron' + |""".stripMargin +} + +sealed trait WebhookEvent extends Event + +final case class Push(branches: Seq[String], tags: Seq[String]) extends WebhookEvent { + + override def render: String = + "push:\n" + + indentOnce { renderBranches + renderTags } + + private def renderBranches = + renderParamWithList("branches", branches) + + def renderTags: String = if (tags.isEmpty) "" else "\n" + renderParamWithList("tags", tags) + +} + +final case class PullRequest(branches: Seq[String], types: Seq[PREventType]) extends WebhookEvent { + + override def render: String = + "pull_request:\n" + + indentOnce { renderBranches + renderTypes } + + private def renderBranches = + renderParamWithList("branches", branches) + + private def renderTypes = + if (types == PREventType.Defaults) "" + else "\n" + renderParamWithList("types", types.map(_.toString).map(SnakeCase.apply)) + +} + +final case class Workflow(name: String, ons: Seq[Event], jobs: Seq[WorkflowJob]) { + + def render: String = + s"""|name: ${wrap(name)} + | + |on:\n${ons.map(_.render).map(indentOnce).mkString("\n")}""".stripMargin +} diff --git a/src/test/scala/sbtghactions/GenerativePluginSpec.scala b/src/test/scala/sbtghactions/GenerativePluginSpec.scala index cef7c5c..ec27eda 100644 --- a/src/test/scala/sbtghactions/GenerativePluginSpec.scala +++ b/src/test/scala/sbtghactions/GenerativePluginSpec.scala @@ -45,7 +45,14 @@ class GenerativePluginSpec extends Specification { |jobs: | """.stripMargin - compileWorkflow("test", List("main"), Nil, PREventType.Defaults, Map(), Nil, "sbt") mustEqual expected + compileWorkflow( + Workflow( + "test", + List(PullRequest(List("main"), PREventType.Defaults), Push(List("main"), Nil)), + Nil + ), + Map(), + "sbt") mustEqual expected } "produce the appropriate skeleton around a zero-job workflow with non-empty tags" in { @@ -62,7 +69,14 @@ class GenerativePluginSpec extends Specification { |jobs: | """.stripMargin - compileWorkflow("test", List("main"), List("howdy"), PREventType.Defaults, Map(), Nil, "sbt") mustEqual expected + compileWorkflow( + Workflow( + "test", + List(PullRequest(List("main"), PREventType.Defaults), Push(List("main"), List("howdy"))), + Nil + ), + Map(), + "sbt") mustEqual expected } "respect non-default pr types" in { @@ -79,7 +93,18 @@ class GenerativePluginSpec extends Specification { |jobs: | """.stripMargin - compileWorkflow("test", List("main"), Nil, List(PREventType.ReadyForReview, PREventType.ReviewRequested, PREventType.Opened), Map(), Nil, "sbt") mustEqual expected + compileWorkflow( + Workflow( + "test", + List( + PullRequest( + List("main"), + List(PREventType.ReadyForReview, PREventType.ReviewRequested, PREventType.Opened)), + Push(List("main"), Nil)), + Nil + ), + Map(), + "sbt") mustEqual expected } "compile a one-job workflow targeting multiple branch patterns with a environment variables" in { @@ -108,17 +133,20 @@ class GenerativePluginSpec extends Specification { | - run: echo Hello World""".stripMargin compileWorkflow( - "test2", - List("main", "backport/v*"), - Nil, - PREventType.Defaults, + Workflow( + "test2", + List( + PullRequest(List("main", "backport/v*"), PREventType.Defaults), + Push(List("main", "backport/v*"), Nil) + ), + List( + WorkflowJob( + "build", + "Build and Test", + List(WorkflowStep.Run(List("echo Hello World"))))) + ), Map( "GITHUB_TOKEN" -> s"$${{ secrets.GITHUB_TOKEN }}"), - List( - WorkflowJob( - "build", - "Build and Test", - List(WorkflowStep.Run(List("echo Hello World"))))), "sbt") mustEqual expected } @@ -155,22 +183,23 @@ class GenerativePluginSpec extends Specification { | steps: | - run: whoami""".stripMargin + compileWorkflow( - "test3", - List("main"), - Nil, - PREventType.Defaults, + Workflow( + "test3", + List(PullRequest(List("main"), PREventType.Defaults), Push(List("main"), Nil)), + List( + WorkflowJob( + "build", + "Build and Test", + List(WorkflowStep.Run(List("echo yikes")))), + + WorkflowJob( + "what", + "If we just didn't", + List(WorkflowStep.Run(List("whoami"))))) + ), Map(), - List( - WorkflowJob( - "build", - "Build and Test", - List(WorkflowStep.Run(List("echo yikes")))), - - WorkflowJob( - "what", - "If we just didn't", - List(WorkflowStep.Run(List("whoami"))))), "") mustEqual expected } @@ -198,18 +227,18 @@ class GenerativePluginSpec extends Specification { | - run: echo yikes""".stripMargin compileWorkflow( - "test4", - List("main"), - Nil, - PREventType.Defaults, + Workflow( + "test4", + List(PullRequest(List("main"), PREventType.Defaults), Push(List("main"), Nil)), + List( + WorkflowJob( + "build", + "Build and Test", + List(WorkflowStep.Run(List("echo yikes"))), + container = Some( + JobContainer("not:real-thing")))) + ), Map(), - List( - WorkflowJob( - "build", - "Build and Test", - List(WorkflowStep.Run(List("echo yikes"))), - container = Some( - JobContainer("not:real-thing")))), "") mustEqual expected } @@ -247,24 +276,24 @@ class GenerativePluginSpec extends Specification { | - run: echo yikes""".stripMargin compileWorkflow( - "test4", - List("main"), - Nil, - PREventType.Defaults, + Workflow( + "test4", + List(PullRequest(List("main"), PREventType.Defaults), Push(List("main"), Nil)), + List( + WorkflowJob( + "build", + "Build and Test", + List(WorkflowStep.Run(List("echo yikes"))), + container = Some( + JobContainer( + "also:not-real", + credentials = Some("janedoe" -> "myvoice"), + env = Map("VERSION" -> "1.0", "PATH" -> "/nope"), + volumes = Map("/source" -> "/dest/ination"), + ports = List(80, 443), + options = List("--cpus", "1"))))) + ), Map(), - List( - WorkflowJob( - "build", - "Build and Test", - List(WorkflowStep.Run(List("echo yikes"))), - container = Some( - JobContainer( - "also:not-real", - credentials = Some("janedoe" -> "myvoice"), - env = Map("VERSION" -> "1.0", "PATH" -> "/nope"), - volumes = Map("/source" -> "/dest/ination"), - ports = List(80, 443), - options = List("--cpus", "1"))))), "") mustEqual expected } } @@ -779,8 +808,8 @@ class GenerativePluginSpec extends Specification { } "predicate compilation" >> { - import RefPredicate._ import Ref._ + import RefPredicate._ "equals" >> { compileBranchPredicate("thingy", Equals(Branch("other"))) mustEqual "thingy == 'refs/heads/other'" From ba3975bacf3707ce8cab69efff923366d90a6b87 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <987Nabil@users.noreply.github.com> Date: Wed, 2 Jun 2021 16:03:56 +0200 Subject: [PATCH 03/23] Separate files for Workflow, TriggerEvent and common RenderFunctions --- .../scala/sbtghactions/GenerativePlugin.scala | 11 +- .../scala/sbtghactions/RenderFunctions.scala | 71 ++++++++++++ .../scala/sbtghactions/TriggerEvent.scala | 73 +++++++++++++ src/main/scala/sbtghactions/Workflow.scala | 101 +----------------- .../sbtghactions/GenerativePluginSpec.scala | 33 ++++-- 5 files changed, 178 insertions(+), 111 deletions(-) create mode 100644 src/main/scala/sbtghactions/RenderFunctions.scala create mode 100644 src/main/scala/sbtghactions/TriggerEvent.scala diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index d925f55..822e765 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -19,7 +19,6 @@ package sbtghactions import sbt._ import sbt.Keys._ - import java.io.{BufferedWriter, FileWriter} import java.nio.file.FileSystems import scala.io.Source @@ -646,8 +645,14 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}""" Workflow( "Continuous Integration", List( - Push(githubWorkflowTargetBranches.value.toList,githubWorkflowTargetTags.value.toList), - PullRequest(githubWorkflowTargetBranches.value.toList,githubWorkflowPREventTypes.value.toList ) + WebhookEvent.Push( + githubWorkflowTargetBranches.value.toList, + githubWorkflowTargetTags.value.toList + ), + WebhookEvent.PullRequest( + githubWorkflowTargetBranches.value.toList, + githubWorkflowPREventTypes.value.toList + ) ), githubWorkflowGeneratedCI.value.toList, ), diff --git a/src/main/scala/sbtghactions/RenderFunctions.scala b/src/main/scala/sbtghactions/RenderFunctions.scala new file mode 100644 index 0000000..9d92dbf --- /dev/null +++ b/src/main/scala/sbtghactions/RenderFunctions.scala @@ -0,0 +1,71 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +object RenderFunctions { + + def wrap(str: String): String = + if (str.indexOf('\n') >= 0) + "|\n" + indent(str, 1) + else if (isSafeString(str)) + str + else + s"'${str.replace("'", "''")}'" + + private def isSafeString(str: String): Boolean = + !(str.indexOf(':') >= 0 || // pretend colon is illegal everywhere for simplicity + str.indexOf('#') >= 0 || // same for comment + str.indexOf('!') == 0 || + str.indexOf('*') == 0 || + str.indexOf('-') == 0 || + str.indexOf('?') == 0 || + str.indexOf('{') == 0 || + str.indexOf('}') == 0 || + str.indexOf('[') == 0 || + str.indexOf(']') == 0 || + str.indexOf(',') == 0 || + str.indexOf('|') == 0 || + str.indexOf('>') == 0 || + str.indexOf('@') == 0 || + str.indexOf('`') == 0 || + str.indexOf('"') == 0 || + str.indexOf('\'') == 0 || + str.indexOf('&') == 0) + + def indentOnce(output: String): String = indent(output, 1) + + def indent(output: String, level: Int): String = { + val space = (0 until level * 2).map(_ => ' ').mkString + (space + output.replace("\n", s"\n$space")).replaceAll("""\n[ ]+\n""", "\n\n") + } + + def renderParamWithList(paramName: String, items: Seq[String]): String = { + val rendered = items.map(wrap) + if (rendered.map(_.length).sum < 40) // just arbitrarily... + rendered.mkString(s"$paramName: [", ", ", "]") + else + rendered.map("- " + _).map(indentOnce).mkString(s"$paramName:\n", "\n", "\n") + } + + object SnakeCase { + private val re = "[A-Z]+".r + + def apply(property: String): String = + re.replaceAllIn(property.head.toLower +: property.tail, { m => s"_${m.matched.toLowerCase}" }) + } + +} diff --git a/src/main/scala/sbtghactions/TriggerEvent.scala b/src/main/scala/sbtghactions/TriggerEvent.scala new file mode 100644 index 0000000..1fd4382 --- /dev/null +++ b/src/main/scala/sbtghactions/TriggerEvent.scala @@ -0,0 +1,73 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +import sbtghactions.RenderFunctions._ + +sealed trait TriggerEvent { + def render: String +} + +//TODO use CronExpr ADT? +final case class Schedule(cron: String) extends TriggerEvent { + + override def render: String = + s""" + |schedule: + | - cron: '$cron' + |""".stripMargin +} + +sealed trait WebhookEvent extends TriggerEvent + +object WebhookEvent { + + final case class Push(branches: Seq[String], tags: Seq[String]) extends WebhookEvent { + + override def render: String = + "push:\n" + + indentOnce { renderBranches + renderTags } + + private def renderBranches = + renderParamWithList("branches", branches) + + def renderTags: String = if (tags.isEmpty) "" else "\n" + renderParamWithList("tags", tags) + + } + + final case class PullRequest(branches: Seq[String], types: Seq[PREventType]) extends WebhookEvent { + + override def render: String = + "pull_request:\n" + + indentOnce { renderBranches + renderTypes } + + private def renderBranches = + renderParamWithList("branches", branches) + + private def renderTypes = + if (types == PREventType.Defaults) "" + else "\n" + renderParamWithList("types", types.map(_.toString).map(SnakeCase.apply)) + + } + +} + + + + + + diff --git a/src/main/scala/sbtghactions/Workflow.scala b/src/main/scala/sbtghactions/Workflow.scala index e230d0a..f027915 100644 --- a/src/main/scala/sbtghactions/Workflow.scala +++ b/src/main/scala/sbtghactions/Workflow.scala @@ -16,106 +16,9 @@ package sbtghactions -import sbtghactions.Help._ +import sbtghactions.RenderFunctions.{indentOnce, wrap} -object Help { - - def wrap(str: String): String = - if (str.indexOf('\n') >= 0) - "|\n" + indent(str, 1) - else if (isSafeString(str)) - str - else - s"'${str.replace("'", "''")}'" - - private def isSafeString(str: String): Boolean = - !(str.indexOf(':') >= 0 || // pretend colon is illegal everywhere for simplicity - str.indexOf('#') >= 0 || // same for comment - str.indexOf('!') == 0 || - str.indexOf('*') == 0 || - str.indexOf('-') == 0 || - str.indexOf('?') == 0 || - str.indexOf('{') == 0 || - str.indexOf('}') == 0 || - str.indexOf('[') == 0 || - str.indexOf(']') == 0 || - str.indexOf(',') == 0 || - str.indexOf('|') == 0 || - str.indexOf('>') == 0 || - str.indexOf('@') == 0 || - str.indexOf('`') == 0 || - str.indexOf('"') == 0 || - str.indexOf('\'') == 0 || - str.indexOf('&') == 0) - - def indentOnce(output: String): String = indent(output, 1) - - def indent(output: String, level: Int): String = { - val space = (0 until level * 2).map(_ => ' ').mkString - (space + output.replace("\n", s"\n$space")).replaceAll("""\n[ ]+\n""", "\n\n") - } - - def renderParamWithList(paramName: String, items: Seq[String]): String = { - val rendered = items.map(wrap) - if (rendered.map(_.length).sum < 40) // just arbitrarily... - rendered.mkString(s"$paramName: [", ", ", "]") - else - rendered.map("- " + _).map(indentOnce).mkString(s"$paramName:\n","\n","\n") - } - - object SnakeCase { - private val re = "[A-Z]+".r - - def apply(property: String): String = - re.replaceAllIn(property.head.toLower +: property.tail, { m => s"_${m.matched.toLowerCase}" }) - } - -} - -sealed trait Event { - def render: String -} - -final case class Schedule(cron: String) extends Event { - - override def render: String = - s""" - |schedule: - | - cron: '$cron' - |""".stripMargin -} - -sealed trait WebhookEvent extends Event - -final case class Push(branches: Seq[String], tags: Seq[String]) extends WebhookEvent { - - override def render: String = - "push:\n" + - indentOnce { renderBranches + renderTags } - - private def renderBranches = - renderParamWithList("branches", branches) - - def renderTags: String = if (tags.isEmpty) "" else "\n" + renderParamWithList("tags", tags) - -} - -final case class PullRequest(branches: Seq[String], types: Seq[PREventType]) extends WebhookEvent { - - override def render: String = - "pull_request:\n" + - indentOnce { renderBranches + renderTypes } - - private def renderBranches = - renderParamWithList("branches", branches) - - private def renderTypes = - if (types == PREventType.Defaults) "" - else "\n" + renderParamWithList("types", types.map(_.toString).map(SnakeCase.apply)) - -} - -final case class Workflow(name: String, ons: Seq[Event], jobs: Seq[WorkflowJob]) { +final case class Workflow(name: String, ons: Seq[TriggerEvent], jobs: Seq[WorkflowJob]) { def render: String = s"""|name: ${wrap(name)} diff --git a/src/test/scala/sbtghactions/GenerativePluginSpec.scala b/src/test/scala/sbtghactions/GenerativePluginSpec.scala index 43e6cd8..12660e9 100644 --- a/src/test/scala/sbtghactions/GenerativePluginSpec.scala +++ b/src/test/scala/sbtghactions/GenerativePluginSpec.scala @@ -48,7 +48,10 @@ class GenerativePluginSpec extends Specification { compileWorkflow( Workflow( "test", - List(PullRequest(List("main"), PREventType.Defaults), Push(List("main"), Nil)), + List( + WebhookEvent.PullRequest(List("main"), PREventType.Defaults), + WebhookEvent.Push(List("main"), Nil) + ), Nil ), Map(), @@ -72,7 +75,10 @@ class GenerativePluginSpec extends Specification { compileWorkflow( Workflow( "test", - List(PullRequest(List("main"), PREventType.Defaults), Push(List("main"), List("howdy"))), + List( + WebhookEvent.PullRequest(List("main"), PREventType.Defaults), + WebhookEvent.Push(List("main"), List("howdy")) + ), Nil ), Map(), @@ -97,10 +103,10 @@ class GenerativePluginSpec extends Specification { Workflow( "test", List( - PullRequest( + WebhookEvent.PullRequest( List("main"), List(PREventType.ReadyForReview, PREventType.ReviewRequested, PREventType.Opened)), - Push(List("main"), Nil)), + WebhookEvent.Push(List("main"), Nil)), Nil ), Map(), @@ -136,8 +142,8 @@ class GenerativePluginSpec extends Specification { Workflow( "test2", List( - PullRequest(List("main", "backport/v*"), PREventType.Defaults), - Push(List("main", "backport/v*"), Nil) + WebhookEvent.PullRequest(List("main", "backport/v*"), PREventType.Defaults), + WebhookEvent.Push(List("main", "backport/v*"), Nil) ), List( WorkflowJob( @@ -187,7 +193,10 @@ class GenerativePluginSpec extends Specification { compileWorkflow( Workflow( "test3", - List(PullRequest(List("main"), PREventType.Defaults), Push(List("main"), Nil)), + List( + WebhookEvent.PullRequest(List("main"), PREventType.Defaults), + WebhookEvent.Push(List("main"), Nil) + ), List( WorkflowJob( "build", @@ -229,7 +238,10 @@ class GenerativePluginSpec extends Specification { compileWorkflow( Workflow( "test4", - List(PullRequest(List("main"), PREventType.Defaults), Push(List("main"), Nil)), + List( + WebhookEvent.PullRequest(List("main"), PREventType.Defaults), + WebhookEvent.Push(List("main"), Nil) + ), List( WorkflowJob( "build", @@ -278,7 +290,10 @@ class GenerativePluginSpec extends Specification { compileWorkflow( Workflow( "test4", - List(PullRequest(List("main"), PREventType.Defaults), Push(List("main"), Nil)), + List( + WebhookEvent.PullRequest(List("main"), PREventType.Defaults), + WebhookEvent.Push(List("main"), Nil) + ), List( WorkflowJob( "build", From 67e746f0a9fcf3466d09fb8eafa8d36387646f51 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <987Nabil@users.noreply.github.com> Date: Thu, 3 Jun 2021 05:24:49 +0200 Subject: [PATCH 04/23] Impl. ManualEvents --- .../scala/sbtghactions/RenderFunctions.scala | 12 ++-- .../scala/sbtghactions/TriggerEvent.scala | 35 +++++++++++ .../scala/sbtghactions/TriggerEventSpec.scala | 61 +++++++++++++++++++ 3 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 src/test/scala/sbtghactions/TriggerEventSpec.scala diff --git a/src/main/scala/sbtghactions/RenderFunctions.scala b/src/main/scala/sbtghactions/RenderFunctions.scala index 9d92dbf..95af072 100644 --- a/src/main/scala/sbtghactions/RenderFunctions.scala +++ b/src/main/scala/sbtghactions/RenderFunctions.scala @@ -50,15 +50,17 @@ object RenderFunctions { def indent(output: String, level: Int): String = { val space = (0 until level * 2).map(_ => ' ').mkString - (space + output.replace("\n", s"\n$space")).replaceAll("""\n[ ]+\n""", "\n\n") + + if (output.isEmpty) "" + else (space + output.replace("\n", s"\n$space")).replaceAll("""\n[ ]+\n""", "\n\n") } def renderParamWithList(paramName: String, items: Seq[String]): String = { val rendered = items.map(wrap) - if (rendered.map(_.length).sum < 40) // just arbitrarily... - rendered.mkString(s"$paramName: [", ", ", "]") - else - rendered.map("- " + _).map(indentOnce).mkString(s"$paramName:\n", "\n", "\n") + + if (rendered.isEmpty) "" + else if (rendered.map(_.length).sum < 40) rendered.mkString(s"$paramName: [", ", ", "]") + else rendered.map("- " + _).map(indentOnce).mkString(s"$paramName:\n", "\n", "\n") } object SnakeCase { diff --git a/src/main/scala/sbtghactions/TriggerEvent.scala b/src/main/scala/sbtghactions/TriggerEvent.scala index 1fd4382..3b142c2 100644 --- a/src/main/scala/sbtghactions/TriggerEvent.scala +++ b/src/main/scala/sbtghactions/TriggerEvent.scala @@ -32,6 +32,41 @@ final case class Schedule(cron: String) extends TriggerEvent { |""".stripMargin } +sealed trait ManualEvents extends TriggerEvent + +object ManualEvents { + + final case class Input( + name: String, + description: String, + default: Option[String], + required: Boolean) { + + //TODO Should we check if the name is a safe string? What if not? + def render: String = + s"""|$name: + | description: ${wrap(description)} + | required: $required + |""".stripMargin + default.map(wrap).map("default: " + _).map(indentOnce).getOrElse("") + } + + final case class WorkflowDispatch(inputs: Seq[Input]) extends ManualEvents { + + override def render: String = + "workflow_dispatch:\n" + renderInputs(inputs) + } + + private def renderInputs(inputs: Seq[Input]) = + if (inputs.isEmpty) "" + else indentOnce { "inputs:\n" + inputs.map(_.render).map(indentOnce).mkString("\n") } + + final case class RepositoryDispatch(types: Seq[String]) extends ManualEvents { + + override def render: String = + s"repository_dispatch:\n" + indentOnce { renderParamWithList("types", types) } + } +} + sealed trait WebhookEvent extends TriggerEvent object WebhookEvent { diff --git a/src/test/scala/sbtghactions/TriggerEventSpec.scala b/src/test/scala/sbtghactions/TriggerEventSpec.scala new file mode 100644 index 0000000..1fefa42 --- /dev/null +++ b/src/test/scala/sbtghactions/TriggerEventSpec.scala @@ -0,0 +1,61 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +import org.specs2.mutable.Specification +import sbtghactions.ManualEvents.Input + +class TriggerEventSpec extends Specification { + "workflow dispatch" should { + "render without inputs" in { + ManualEvents.WorkflowDispatch(Nil).render mustEqual "workflow_dispatch:\n" + } + + "render inputs" in { + + val expected = + """workflow_dispatch: + | inputs: + | ref: + | description: The branch, tag or SHA to build + | required: true + | default: master""".stripMargin + + ManualEvents.WorkflowDispatch( + List( + Input("ref", "The branch, tag or SHA to build", Some("master"), required = true) + ) + ).render mustEqual expected + } + } + + "repository dispatch" should { + "render without types" in { + ManualEvents.RepositoryDispatch(Nil).render mustEqual "repository_dispatch:\n" + } + + "render types" in { + + val expected = + """repository_dispatch: + | types: [event1, event2]""".stripMargin + + ManualEvents.RepositoryDispatch(List("event1", "event2")).render mustEqual expected + } + } + +} From df528f36e840b628a47b83c0fde449e750340c31 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <987Nabil@users.noreply.github.com> Date: Mon, 7 Jun 2021 11:34:08 +0200 Subject: [PATCH 05/23] Impl. WebhookEvents - All TriggerEvents have now a corresponding case class/object --- .../sbtghactions/CheckRunEventType.scala | 25 ++ .../sbtghactions/CheckSuiteEventType.scala | 25 ++ src/main/scala/sbtghactions/EventType.scala | 23 ++ .../scala/sbtghactions/GenerativePlugin.scala | 1 + .../sbtghactions/IssueCommentEventType.scala | 25 ++ .../scala/sbtghactions/IssuesEventType.scala | 38 +++ .../scala/sbtghactions/LabelEventType.scala | 25 ++ .../sbtghactions/MilestoneEventType.scala | 27 ++ src/main/scala/sbtghactions/PREventType.scala | 28 +- .../PRReviewCommentEventType.scala | 25 ++ .../sbtghactions/PRReviewEventType.scala | 25 ++ .../sbtghactions/PRTargetEventType.scala | 36 +++ .../sbtghactions/ProjectCardEventType.scala | 27 ++ .../sbtghactions/ProjectColumnEventType.scala | 26 ++ .../scala/sbtghactions/ProjectEventType.scala | 28 ++ .../RegistryPackageEventType.scala | 24 ++ .../scala/sbtghactions/ReleaseEventType.scala | 29 ++ .../scala/sbtghactions/RenderFunctions.scala | 14 +- .../scala/sbtghactions/TriggerEvent.scala | 112 ++++++-- .../scala/sbtghactions/WatchEventType.scala | 23 ++ .../sbtghactions/WorkflowRunEventType.scala | 24 ++ .../sbtghactions/GenerativePluginSpec.scala | 13 +- .../scala/sbtghactions/TriggerEventSpec.scala | 255 +++++++++++++++++- 23 files changed, 818 insertions(+), 60 deletions(-) create mode 100644 src/main/scala/sbtghactions/CheckRunEventType.scala create mode 100644 src/main/scala/sbtghactions/CheckSuiteEventType.scala create mode 100644 src/main/scala/sbtghactions/EventType.scala create mode 100644 src/main/scala/sbtghactions/IssueCommentEventType.scala create mode 100644 src/main/scala/sbtghactions/IssuesEventType.scala create mode 100644 src/main/scala/sbtghactions/LabelEventType.scala create mode 100644 src/main/scala/sbtghactions/MilestoneEventType.scala create mode 100644 src/main/scala/sbtghactions/PRReviewCommentEventType.scala create mode 100644 src/main/scala/sbtghactions/PRReviewEventType.scala create mode 100644 src/main/scala/sbtghactions/PRTargetEventType.scala create mode 100644 src/main/scala/sbtghactions/ProjectCardEventType.scala create mode 100644 src/main/scala/sbtghactions/ProjectColumnEventType.scala create mode 100644 src/main/scala/sbtghactions/ProjectEventType.scala create mode 100644 src/main/scala/sbtghactions/RegistryPackageEventType.scala create mode 100644 src/main/scala/sbtghactions/ReleaseEventType.scala create mode 100644 src/main/scala/sbtghactions/WatchEventType.scala create mode 100644 src/main/scala/sbtghactions/WorkflowRunEventType.scala diff --git a/src/main/scala/sbtghactions/CheckRunEventType.scala b/src/main/scala/sbtghactions/CheckRunEventType.scala new file mode 100644 index 0000000..2edd5e4 --- /dev/null +++ b/src/main/scala/sbtghactions/CheckRunEventType.scala @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait CheckRunEventType extends EventType + +object CheckRunEventType { + case object Created extends CheckRunEventType + case object Rerequested extends CheckRunEventType + case object Completed extends CheckRunEventType +} diff --git a/src/main/scala/sbtghactions/CheckSuiteEventType.scala b/src/main/scala/sbtghactions/CheckSuiteEventType.scala new file mode 100644 index 0000000..dde727b --- /dev/null +++ b/src/main/scala/sbtghactions/CheckSuiteEventType.scala @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait CheckSuiteEventType extends EventType + +object CheckSuiteEventType { + case object Completed extends CheckSuiteEventType + case object Requested extends CheckSuiteEventType + case object Rerequested extends CheckSuiteEventType +} diff --git a/src/main/scala/sbtghactions/EventType.scala b/src/main/scala/sbtghactions/EventType.scala new file mode 100644 index 0000000..0482a55 --- /dev/null +++ b/src/main/scala/sbtghactions/EventType.scala @@ -0,0 +1,23 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +import sbtghactions.RenderFunctions.SnakeCase + +trait EventType extends Product with Serializable { + def render: String = SnakeCase(productPrefix) +} diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index 822e765..b345545 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -651,6 +651,7 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}""" ), WebhookEvent.PullRequest( githubWorkflowTargetBranches.value.toList, + githubWorkflowTargetTags.value.toList, githubWorkflowPREventTypes.value.toList ) ), diff --git a/src/main/scala/sbtghactions/IssueCommentEventType.scala b/src/main/scala/sbtghactions/IssueCommentEventType.scala new file mode 100644 index 0000000..f64829d --- /dev/null +++ b/src/main/scala/sbtghactions/IssueCommentEventType.scala @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait IssueCommentEventType extends EventType + +object IssueCommentEventType { + case object Created extends IssueCommentEventType + case object Edited extends IssueCommentEventType + case object Deleted extends IssueCommentEventType +} diff --git a/src/main/scala/sbtghactions/IssuesEventType.scala b/src/main/scala/sbtghactions/IssuesEventType.scala new file mode 100644 index 0000000..d41362c --- /dev/null +++ b/src/main/scala/sbtghactions/IssuesEventType.scala @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait IssuesEventType extends EventType + +object IssuesEventType { + case object Opened extends IssuesEventType + case object Edited extends IssuesEventType + case object Deleted extends IssuesEventType + case object Transferred extends IssuesEventType + case object Pinned extends IssuesEventType + case object Unpinned extends IssuesEventType + case object Closed extends IssuesEventType + case object Reopened extends IssuesEventType + case object Assigned extends IssuesEventType + case object Unassigned extends IssuesEventType + case object Labeled extends IssuesEventType + case object Unlabeled extends IssuesEventType + case object Locked extends IssuesEventType + case object Unlocked extends IssuesEventType + case object Milestoned extends IssuesEventType + case object Demilestoned extends IssuesEventType +} diff --git a/src/main/scala/sbtghactions/LabelEventType.scala b/src/main/scala/sbtghactions/LabelEventType.scala new file mode 100644 index 0000000..1f86134 --- /dev/null +++ b/src/main/scala/sbtghactions/LabelEventType.scala @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait LabelEventType extends EventType + +object LabelEventType { + case object Created extends LabelEventType + case object Edited extends LabelEventType + case object Deleted extends LabelEventType +} diff --git a/src/main/scala/sbtghactions/MilestoneEventType.scala b/src/main/scala/sbtghactions/MilestoneEventType.scala new file mode 100644 index 0000000..59e93cd --- /dev/null +++ b/src/main/scala/sbtghactions/MilestoneEventType.scala @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait MilestoneEventType extends EventType + +object MilestoneEventType { + case object Created extends MilestoneEventType + case object Closed extends MilestoneEventType + case object Opened extends MilestoneEventType + case object Edited extends MilestoneEventType + case object Deleted extends MilestoneEventType +} diff --git a/src/main/scala/sbtghactions/PREventType.scala b/src/main/scala/sbtghactions/PREventType.scala index 7560e7f..07fabc9 100644 --- a/src/main/scala/sbtghactions/PREventType.scala +++ b/src/main/scala/sbtghactions/PREventType.scala @@ -16,23 +16,23 @@ package sbtghactions -sealed trait PREventType extends Product with Serializable +sealed trait PREventType extends EventType object PREventType { val Defaults = List(Opened, Reopened, Synchronize) - case object Assigned extends PREventType - case object Unassigned extends PREventType - case object Labeled extends PREventType - case object Unlabeled extends PREventType - case object Opened extends PREventType - case object Edited extends PREventType - case object Closed extends PREventType - case object Reopened extends PREventType - case object Synchronize extends PREventType - case object ReadyForReview extends PREventType - case object Locked extends PREventType - case object Unlocked extends PREventType - case object ReviewRequested extends PREventType + case object Assigned extends PREventType + case object Unassigned extends PREventType + case object Labeled extends PREventType + case object Unlabeled extends PREventType + case object Opened extends PREventType + case object Edited extends PREventType + case object Closed extends PREventType + case object Reopened extends PREventType + case object Synchronize extends PREventType + case object ReadyForReview extends PREventType + case object Locked extends PREventType + case object Unlocked extends PREventType + case object ReviewRequested extends PREventType case object ReviewRequestRemoved extends PREventType } diff --git a/src/main/scala/sbtghactions/PRReviewCommentEventType.scala b/src/main/scala/sbtghactions/PRReviewCommentEventType.scala new file mode 100644 index 0000000..f47613a --- /dev/null +++ b/src/main/scala/sbtghactions/PRReviewCommentEventType.scala @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait PRReviewCommentEventType extends EventType + +object PRReviewCommentEventType { + case object Created extends PRReviewCommentEventType + case object Edited extends PRReviewCommentEventType + case object Deleted extends PRReviewCommentEventType +} diff --git a/src/main/scala/sbtghactions/PRReviewEventType.scala b/src/main/scala/sbtghactions/PRReviewEventType.scala new file mode 100644 index 0000000..3e2bbb4 --- /dev/null +++ b/src/main/scala/sbtghactions/PRReviewEventType.scala @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait PRReviewEventType extends EventType + +object PRReviewEventType { + case object Submitted extends PRReviewEventType + case object Edited extends PRReviewEventType + case object Dismissed extends PRReviewEventType +} diff --git a/src/main/scala/sbtghactions/PRTargetEventType.scala b/src/main/scala/sbtghactions/PRTargetEventType.scala new file mode 100644 index 0000000..3d7f742 --- /dev/null +++ b/src/main/scala/sbtghactions/PRTargetEventType.scala @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait PRTargetEventType extends EventType + +object PRTargetEventType { + case object Assigned extends PRTargetEventType + case object Unassigned extends PRTargetEventType + case object Labeled extends PRTargetEventType + case object Unlabeled extends PRTargetEventType + case object Opened extends PRTargetEventType + case object Edited extends PRTargetEventType + case object Closed extends PRTargetEventType + case object Reopened extends PRTargetEventType + case object Synchronize extends PRTargetEventType + case object ReadyForReview extends PRTargetEventType + case object Locked extends PRTargetEventType + case object Unlocked extends PRTargetEventType + case object ReviewRequested extends PRTargetEventType + case object ReviewRequestRemoved extends PRTargetEventType +} diff --git a/src/main/scala/sbtghactions/ProjectCardEventType.scala b/src/main/scala/sbtghactions/ProjectCardEventType.scala new file mode 100644 index 0000000..42d4faf --- /dev/null +++ b/src/main/scala/sbtghactions/ProjectCardEventType.scala @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait ProjectCardEventType extends EventType + +object ProjectCardEventType { + case object Created extends ProjectCardEventType + case object Moved extends ProjectCardEventType + case object ConvertedToAnIssue extends ProjectCardEventType + case object Edited extends ProjectCardEventType + case object Deleted extends ProjectCardEventType +} diff --git a/src/main/scala/sbtghactions/ProjectColumnEventType.scala b/src/main/scala/sbtghactions/ProjectColumnEventType.scala new file mode 100644 index 0000000..231ef0e --- /dev/null +++ b/src/main/scala/sbtghactions/ProjectColumnEventType.scala @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait ProjectColumnEventType extends EventType + +object ProjectColumnEventType { + case object Created extends ProjectColumnEventType + case object Updated extends ProjectColumnEventType + case object Moved extends ProjectColumnEventType + case object Deleted extends ProjectColumnEventType +} diff --git a/src/main/scala/sbtghactions/ProjectEventType.scala b/src/main/scala/sbtghactions/ProjectEventType.scala new file mode 100644 index 0000000..2a80f7d --- /dev/null +++ b/src/main/scala/sbtghactions/ProjectEventType.scala @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait ProjectEventType extends EventType + +object ProjectEventType { + case object Created extends ProjectEventType + case object Updated extends ProjectEventType + case object Closed extends ProjectEventType + case object Reopened extends ProjectEventType + case object Edited extends ProjectEventType + case object Deleted extends ProjectEventType +} diff --git a/src/main/scala/sbtghactions/RegistryPackageEventType.scala b/src/main/scala/sbtghactions/RegistryPackageEventType.scala new file mode 100644 index 0000000..5d764c1 --- /dev/null +++ b/src/main/scala/sbtghactions/RegistryPackageEventType.scala @@ -0,0 +1,24 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait RegistryPackageEventType extends EventType + +object RegistryPackageEventType { + case object Published extends RegistryPackageEventType + case object Updated extends RegistryPackageEventType +} diff --git a/src/main/scala/sbtghactions/ReleaseEventType.scala b/src/main/scala/sbtghactions/ReleaseEventType.scala new file mode 100644 index 0000000..dc0851a --- /dev/null +++ b/src/main/scala/sbtghactions/ReleaseEventType.scala @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait ReleaseEventType extends EventType + +object ReleaseEventType { + case object Published extends ReleaseEventType + case object Unpublished extends ReleaseEventType + case object Created extends ReleaseEventType + case object Edited extends ReleaseEventType + case object Deleted extends ReleaseEventType + case object Prereleased extends ReleaseEventType + case object Released extends ReleaseEventType +} diff --git a/src/main/scala/sbtghactions/RenderFunctions.scala b/src/main/scala/sbtghactions/RenderFunctions.scala index 95af072..3208e03 100644 --- a/src/main/scala/sbtghactions/RenderFunctions.scala +++ b/src/main/scala/sbtghactions/RenderFunctions.scala @@ -18,6 +18,13 @@ package sbtghactions object RenderFunctions { + def renderBranches(branches: Seq[String]): String = + renderParamWithList("branches", branches) + + def renderTypes(types: Seq[EventType]): String = + if (types.isEmpty) "" + else indentOnce { renderParamWithList("types", types.map(_.render)) } + def wrap(str: String): String = if (str.indexOf('\n') >= 0) "|\n" + indent(str, 1) @@ -50,17 +57,18 @@ object RenderFunctions { def indent(output: String, level: Int): String = { val space = (0 until level * 2).map(_ => ' ').mkString + val nlPrefixCount = output.takeWhile(_ == '\n').length if (output.isEmpty) "" - else (space + output.replace("\n", s"\n$space")).replaceAll("""\n[ ]+\n""", "\n\n") + else "\n" * nlPrefixCount + (space + output.drop(nlPrefixCount).replace("\n", s"\n$space")).replaceAll("""\n[ ]+\n""", "\n\n") } def renderParamWithList(paramName: String, items: Seq[String]): String = { val rendered = items.map(wrap) if (rendered.isEmpty) "" - else if (rendered.map(_.length).sum < 40) rendered.mkString(s"$paramName: [", ", ", "]") - else rendered.map("- " + _).map(indentOnce).mkString(s"$paramName:\n", "\n", "\n") + else if (rendered.map(_.length).sum < 40) rendered.mkString(s"\n$paramName: [", ", ", "]") + else rendered.map("- " + _).map(indentOnce).mkString(s"\n$paramName:\n", "\n", "\n") } object SnakeCase { diff --git a/src/main/scala/sbtghactions/TriggerEvent.scala b/src/main/scala/sbtghactions/TriggerEvent.scala index 3b142c2..f967e06 100644 --- a/src/main/scala/sbtghactions/TriggerEvent.scala +++ b/src/main/scala/sbtghactions/TriggerEvent.scala @@ -18,23 +18,21 @@ package sbtghactions import sbtghactions.RenderFunctions._ -sealed trait TriggerEvent { +sealed trait TriggerEvent extends Product with Serializable { + val name: String = SnakeCase(productPrefix) def render: String } -//TODO use CronExpr ADT? final case class Schedule(cron: String) extends TriggerEvent { override def render: String = - s""" - |schedule: - | - cron: '$cron' - |""".stripMargin + s"""|$name: + | - cron: '$cron'""".stripMargin } -sealed trait ManualEvents extends TriggerEvent +sealed trait ManualEvent extends TriggerEvent -object ManualEvents { +object ManualEvent { final case class Input( name: String, @@ -50,59 +48,117 @@ object ManualEvents { |""".stripMargin + default.map(wrap).map("default: " + _).map(indentOnce).getOrElse("") } - final case class WorkflowDispatch(inputs: Seq[Input]) extends ManualEvents { + final case class WorkflowDispatch(inputs: Seq[Input]) extends ManualEvent { override def render: String = - "workflow_dispatch:\n" + renderInputs(inputs) + s"$name:${renderInputs(inputs)}" } private def renderInputs(inputs: Seq[Input]) = if (inputs.isEmpty) "" - else indentOnce { "inputs:\n" + inputs.map(_.render).map(indentOnce).mkString("\n") } + else indentOnce { "\ninputs:\n" + inputs.map(_.render).map(indentOnce).mkString("\n") } - final case class RepositoryDispatch(types: Seq[String]) extends ManualEvents { + final case class RepositoryDispatch(types: Seq[String]) extends ManualEvent { override def render: String = - s"repository_dispatch:\n" + indentOnce { renderParamWithList("types", types) } + s"$name:${indentOnce { renderParamWithList("types", types) }}" } } sealed trait WebhookEvent extends TriggerEvent +sealed trait PlainNameEvent extends WebhookEvent { + final override def render: String = name +} + +sealed trait TypedEvent extends WebhookEvent { + + def types: Seq[EventType] + + final override def render: String = + s"$name:${renderTypes(types)}" +} + object WebhookEvent { - final case class Push(branches: Seq[String], tags: Seq[String]) extends WebhookEvent { + final case class CheckRun(types: Seq[CheckRunEventType]) extends TypedEvent - override def render: String = - "push:\n" + - indentOnce { renderBranches + renderTags } + final case class CheckSuite(types: Seq[CheckSuiteEventType]) extends TypedEvent - private def renderBranches = - renderParamWithList("branches", branches) + case object Create extends PlainNameEvent - def renderTags: String = if (tags.isEmpty) "" else "\n" + renderParamWithList("tags", tags) + case object Delete extends PlainNameEvent - } + case object Deployment extends PlainNameEvent + + case object DeploymentStatus extends PlainNameEvent + + case object Fork extends PlainNameEvent + + case object Gollum extends PlainNameEvent + + final case class IssueComment(types: Seq[IssueCommentEventType]) extends TypedEvent + + final case class Issues(types: Seq[IssuesEventType]) extends TypedEvent + + final case class Label(types: Seq[LabelEventType]) extends TypedEvent + + final case class Milestone(types: Seq[MilestoneEventType]) extends TypedEvent + + case object PageBuild extends PlainNameEvent - final case class PullRequest(branches: Seq[String], types: Seq[PREventType]) extends WebhookEvent { + final case class Project(types: Seq[ProjectEventType]) extends TypedEvent + + final case class ProjectCard(types: Seq[ProjectCardEventType]) extends TypedEvent + + final case class ProjectColumn(types: Seq[ProjectColumnEventType]) extends TypedEvent + + case object Public extends PlainNameEvent + + final case class PullRequest(branches: Seq[String], tags: Seq[String], types: Seq[PREventType]) + extends WebhookEvent { override def render: String = - "pull_request:\n" + - indentOnce { renderBranches + renderTypes } + s"$name:" + + indentOnce { renderBranches(branches) + renderTags + renderTypes } - private def renderBranches = - renderParamWithList("branches", branches) + private def renderTags = + renderParamWithList("tags", tags) private def renderTypes = if (types == PREventType.Defaults) "" - else "\n" + renderParamWithList("types", types.map(_.toString).map(SnakeCase.apply)) + else "" + renderParamWithList("types", types.map(_.render)) } -} + final case class PullRequestReview(types: Seq[PRReviewEventType]) extends TypedEvent + final case class PullRequestReviewComment(types: Seq[PRReviewCommentEventType]) extends TypedEvent + final case class PullRequestTarget(types: Seq[PRTargetEventType]) extends TypedEvent + final case class Push(branches: Seq[String], tags: Seq[String]) extends WebhookEvent { + + override def render: String = + s"$name:" + + indentOnce { renderBranches(branches) + renderTags } + def renderTags: String = if (tags.isEmpty) "" else renderParamWithList("tags", tags) + } + final case class RegistryPackage(types: Seq[RegistryPackageEventType]) extends TypedEvent + + final case class Release(types: Seq[ReleaseEventType]) extends TypedEvent + + case object Status extends PlainNameEvent + + final case class Watch(types: Seq[WatchEventType]) extends TypedEvent + + final case class WorkflowRun(workflows: Seq[String], types: Seq[WorkflowRunEventType]) + extends WebhookEvent { + + override def render: String = + s"$name:${indentOnce(renderParamWithList("workflows", workflows))}${renderTypes(types)}" + } +} diff --git a/src/main/scala/sbtghactions/WatchEventType.scala b/src/main/scala/sbtghactions/WatchEventType.scala new file mode 100644 index 0000000..e5e4fa8 --- /dev/null +++ b/src/main/scala/sbtghactions/WatchEventType.scala @@ -0,0 +1,23 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait WatchEventType extends EventType + +object WatchEventType { + case object Started extends WatchEventType +} diff --git a/src/main/scala/sbtghactions/WorkflowRunEventType.scala b/src/main/scala/sbtghactions/WorkflowRunEventType.scala new file mode 100644 index 0000000..1eac3b9 --- /dev/null +++ b/src/main/scala/sbtghactions/WorkflowRunEventType.scala @@ -0,0 +1,24 @@ +/* + * Copyright 2020 Daniel Spiewak + * + * 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 sbtghactions + +sealed trait WorkflowRunEventType extends EventType + +object WorkflowRunEventType { + case object Completed extends WorkflowRunEventType + case object Requested extends WorkflowRunEventType +} diff --git a/src/test/scala/sbtghactions/GenerativePluginSpec.scala b/src/test/scala/sbtghactions/GenerativePluginSpec.scala index 12660e9..049f97b 100644 --- a/src/test/scala/sbtghactions/GenerativePluginSpec.scala +++ b/src/test/scala/sbtghactions/GenerativePluginSpec.scala @@ -49,7 +49,7 @@ class GenerativePluginSpec extends Specification { Workflow( "test", List( - WebhookEvent.PullRequest(List("main"), PREventType.Defaults), + WebhookEvent.PullRequest(List("main"), Nil, PREventType.Defaults), WebhookEvent.Push(List("main"), Nil) ), Nil @@ -76,7 +76,7 @@ class GenerativePluginSpec extends Specification { Workflow( "test", List( - WebhookEvent.PullRequest(List("main"), PREventType.Defaults), + WebhookEvent.PullRequest(List("main"), Nil, PREventType.Defaults), WebhookEvent.Push(List("main"), List("howdy")) ), Nil @@ -105,6 +105,7 @@ class GenerativePluginSpec extends Specification { List( WebhookEvent.PullRequest( List("main"), + Nil, List(PREventType.ReadyForReview, PREventType.ReviewRequested, PREventType.Opened)), WebhookEvent.Push(List("main"), Nil)), Nil @@ -142,7 +143,7 @@ class GenerativePluginSpec extends Specification { Workflow( "test2", List( - WebhookEvent.PullRequest(List("main", "backport/v*"), PREventType.Defaults), + WebhookEvent.PullRequest(List("main", "backport/v*"), Nil, PREventType.Defaults), WebhookEvent.Push(List("main", "backport/v*"), Nil) ), List( @@ -194,7 +195,7 @@ class GenerativePluginSpec extends Specification { Workflow( "test3", List( - WebhookEvent.PullRequest(List("main"), PREventType.Defaults), + WebhookEvent.PullRequest(List("main"), Nil, PREventType.Defaults), WebhookEvent.Push(List("main"), Nil) ), List( @@ -239,7 +240,7 @@ class GenerativePluginSpec extends Specification { Workflow( "test4", List( - WebhookEvent.PullRequest(List("main"), PREventType.Defaults), + WebhookEvent.PullRequest(List("main"), Nil, PREventType.Defaults), WebhookEvent.Push(List("main"), Nil) ), List( @@ -291,7 +292,7 @@ class GenerativePluginSpec extends Specification { Workflow( "test4", List( - WebhookEvent.PullRequest(List("main"), PREventType.Defaults), + WebhookEvent.PullRequest(List("main"), Nil, PREventType.Defaults), WebhookEvent.Push(List("main"), Nil) ), List( diff --git a/src/test/scala/sbtghactions/TriggerEventSpec.scala b/src/test/scala/sbtghactions/TriggerEventSpec.scala index 1fefa42..89e8025 100644 --- a/src/test/scala/sbtghactions/TriggerEventSpec.scala +++ b/src/test/scala/sbtghactions/TriggerEventSpec.scala @@ -17,12 +17,19 @@ package sbtghactions import org.specs2.mutable.Specification -import sbtghactions.ManualEvents.Input +import org.specs2.specification.AllExpectations +import sbtghactions.ManualEvent.Input + +class TriggerEventSpec extends Specification with AllExpectations { + "schedule" should { + "render cron expression" in { + Schedule("* * * * *").render mustEqual "schedule:\n - cron: '* * * * *'" + } + } -class TriggerEventSpec extends Specification { "workflow dispatch" should { "render without inputs" in { - ManualEvents.WorkflowDispatch(Nil).render mustEqual "workflow_dispatch:\n" + ManualEvent.WorkflowDispatch(Nil).render mustEqual "workflow_dispatch:" } "render inputs" in { @@ -35,17 +42,19 @@ class TriggerEventSpec extends Specification { | required: true | default: master""".stripMargin - ManualEvents.WorkflowDispatch( - List( - Input("ref", "The branch, tag or SHA to build", Some("master"), required = true) + ManualEvent + .WorkflowDispatch( + List( + Input("ref", "The branch, tag or SHA to build", Some("master"), required = true) ) - ).render mustEqual expected + ) + .render mustEqual expected } } "repository dispatch" should { "render without types" in { - ManualEvents.RepositoryDispatch(Nil).render mustEqual "repository_dispatch:\n" + ManualEvent.RepositoryDispatch(Nil).render mustEqual "repository_dispatch:" } "render types" in { @@ -54,8 +63,236 @@ class TriggerEventSpec extends Specification { """repository_dispatch: | types: [event1, event2]""".stripMargin - ManualEvents.RepositoryDispatch(List("event1", "event2")).render mustEqual expected + ManualEvent.RepositoryDispatch(List("event1", "event2")).render mustEqual expected + } + } + + "pull request" should { + "render without branches, tags or types" in { + val expected = "pull_request:" + WebhookEvent.PullRequest(Nil, Nil, Nil).render mustEqual expected + } + + "render without branches, tags, but with types" in { + val expected = + """|pull_request: + | types: [edited, ready_for_review]""".stripMargin + WebhookEvent + .PullRequest(Nil, Nil, List(PREventType.Edited, PREventType.ReadyForReview)) + .render mustEqual expected + } + + "render without branches, but with tags and types" in { + val expected = + """|pull_request: + | tags: [v1*, v2*] + | types: [edited, ready_for_review]""".stripMargin + WebhookEvent + .PullRequest(Nil, List("v1*", "v2*"), List(PREventType.Edited, PREventType.ReadyForReview)) + .render mustEqual expected + } + + "render with branches, tags and types" in { + val expected = + """|pull_request: + | branches: [master] + | tags: [v1*, v2*] + | types: [edited, ready_for_review]""".stripMargin + WebhookEvent + .PullRequest( + List("master"), + List("v1*", "v2*"), + List(PREventType.Edited, PREventType.ReadyForReview) + ) + .render mustEqual expected + } + + "render without types, but with branches and tags" in { + val expected = + """|pull_request: + | branches: [master] + | tags: [v1*, v2*]""".stripMargin + WebhookEvent.PullRequest(List("master"), List("v1*", "v2*"), Nil).render mustEqual expected + } + + "render without tags, but with branches and types" in { + val expected = + """|pull_request: + | branches: [master] + | types: [edited, ready_for_review]""".stripMargin + WebhookEvent + .PullRequest(List("master"), Nil, List(PREventType.Edited, PREventType.ReadyForReview)) + .render mustEqual expected + } + } + + "push" should { + "render without branches, tags or types" in { + val expected = "push:" + WebhookEvent.Push(Nil, Nil).render mustEqual expected + } + + "render without branches, but with tags and types" in { + val expected = + """|push: + | tags: [v1*, v2*]""".stripMargin + WebhookEvent.Push(Nil, List("v1*", "v2*")).render mustEqual expected + } + + "render with branches and tags" in { + val expected = + """|push: + | branches: [master] + | tags: [v1*, v2*]""".stripMargin + WebhookEvent.Push(List("master"), List("v1*", "v2*")).render mustEqual expected + } + + "render without tags, but with branches" in { + val expected = + """|push: + | branches: [master]""".stripMargin + WebhookEvent.Push(List("master"), Nil).render mustEqual expected + } + } + + "workflow run" should { + "render without workflows and types" in { + val expected = "workflow_run:" + WebhookEvent.WorkflowRun(Nil, Nil).render mustEqual expected + } + } + + "plain name events" should { + "render their name only" in { + + val nameEvents = List( + (WebhookEvent.Create, "create"), + (WebhookEvent.Delete, "delete"), + (WebhookEvent.Deployment, "deployment"), + (WebhookEvent.DeploymentStatus, "deployment_status"), + (WebhookEvent.Fork, "fork"), + (WebhookEvent.Gollum, "gollum"), + (WebhookEvent.PageBuild, "page_build"), + (WebhookEvent.Public, "public"), + (WebhookEvent.Status, "status"), + ) + + forall(nameEvents) { case (event, name) => + event.render mustEqual s"$name" + } + } } + "typed events" should { + "render their name only, if no types are given" in { + + val events = List( + (WebhookEvent.CheckRun(Nil), "check_run:"), + (WebhookEvent.CheckSuite(Nil), "check_suite:"), + (WebhookEvent.IssueComment(Nil), "issue_comment:"), + (WebhookEvent.Issues(Nil), "issues:"), + (WebhookEvent.Label(Nil), "label:"), + (WebhookEvent.Milestone(Nil), "milestone:"), + (WebhookEvent.Project(Nil), "project:"), + (WebhookEvent.ProjectCard(Nil), "project_card:"), + (WebhookEvent.ProjectColumn(Nil), "project_column:"), + (WebhookEvent.PullRequestReview(Nil), "pull_request_review:"), + (WebhookEvent.PullRequestReviewComment(Nil), "pull_request_review_comment:"), + (WebhookEvent.PullRequestTarget(Nil), "pull_request_target:"), + (WebhookEvent.RegistryPackage(Nil), "registry_package:"), + (WebhookEvent.Release(Nil), "release:"), + (WebhookEvent.Watch(Nil), "watch:"), + ) + + forall(events) { case (event, rendered) => + event.render mustEqual s"$rendered" + } + + } + + "render their name only, if no types are given" in { + + val events = List( + ( + WebhookEvent.CheckRun(Seq(CheckRunEventType.Created, CheckRunEventType.Completed)), + "check_run:\n types: [created, completed]" + ), + ( + WebhookEvent.CheckSuite( + Seq(CheckSuiteEventType.Requested, CheckSuiteEventType.Completed) + ), + "check_suite:\n types: [requested, completed]" + ), + ( + WebhookEvent.IssueComment( + Seq(IssueCommentEventType.Created, IssueCommentEventType.Edited) + ), + "issue_comment:\n types: [created, edited]" + ), + ( + WebhookEvent.Issues(Seq(IssuesEventType.Opened, IssuesEventType.Edited)), + "issues:\n types: [opened, edited]" + ), + ( + WebhookEvent.Label(Seq(LabelEventType.Created, LabelEventType.Edited)), + "label:\n types: [created, edited]" + ), + ( + WebhookEvent.Milestone(Seq(MilestoneEventType.Opened, MilestoneEventType.Closed)), + "milestone:\n types: [opened, closed]" + ), + ( + WebhookEvent.Project(Seq(ProjectEventType.Created, ProjectEventType.Closed)), + "project:\n types: [created, closed]" + ), + ( + WebhookEvent.ProjectCard( + Seq(ProjectCardEventType.ConvertedToAnIssue, ProjectCardEventType.Moved) + ), + "project_card:\n types: [converted_to_an_issue, moved]" + ), + ( + WebhookEvent.ProjectColumn( + Seq(ProjectColumnEventType.Moved, ProjectColumnEventType.Created) + ), + "project_column:\n types: [moved, created]" + ), + ( + WebhookEvent.PullRequestReview( + Seq(PRReviewEventType.Edited, PRReviewEventType.Dismissed) + ), + "pull_request_review:\n types: [edited, dismissed]" + ), + ( + WebhookEvent.PullRequestReviewComment( + Seq(PRReviewCommentEventType.Created, PRReviewCommentEventType.Edited) + ), + "pull_request_review_comment:\n types: [created, edited]" + ), + ( + WebhookEvent.PullRequestTarget( + Seq(PRTargetEventType.Edited, PRTargetEventType.ReadyForReview) + ), + "pull_request_target:\n types: [edited, ready_for_review]" + ), + ( + WebhookEvent.RegistryPackage( + Seq(RegistryPackageEventType.Updated, RegistryPackageEventType.Published) + ), + "registry_package:\n types: [updated, published]" + ), + ( + WebhookEvent.Release(Seq(ReleaseEventType.Edited, ReleaseEventType.Unpublished)), + "release:\n types: [edited, unpublished]" + ), + (WebhookEvent.Watch(Seq(WatchEventType.Started)), "watch:\n types: [started]"), + ) + + forall(events) { case (event, rendered) => + event.render mustEqual s"$rendered" + } + + } + } } From d827cca0f92b76639e56a1faa9072836c7cc9e9f Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <987Nabil@users.noreply.github.com> Date: Mon, 7 Jun 2021 14:00:10 +0200 Subject: [PATCH 06/23] Generate custom workflows - Add new sbt key githubWorkflowCustomWorkflows for custom workflows - Generate custom workflows within githubWorkflowGenerate --- .../scala/sbtghactions/GenerativeKeys.scala | 2 + .../scala/sbtghactions/GenerativePlugin.scala | 40 ++++++++++++++----- src/main/scala/sbtghactions/Workflow.scala | 6 ++- .../sbtghactions/GenerativePluginSpec.scala | 40 +++++++++---------- 4 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/main/scala/sbtghactions/GenerativeKeys.scala b/src/main/scala/sbtghactions/GenerativeKeys.scala index cbed059..aca8958 100644 --- a/src/main/scala/sbtghactions/GenerativeKeys.scala +++ b/src/main/scala/sbtghactions/GenerativeKeys.scala @@ -28,6 +28,8 @@ trait GenerativeKeys { lazy val githubWorkflowGeneratedDownloadSteps = settingKey[Seq[WorkflowStep]]("The sequence of steps used to download intermediate build artifacts published by an adjacent job") lazy val githubWorkflowGeneratedCacheSteps = settingKey[Seq[WorkflowStep]]("The sequence of steps used to configure caching for ivy, sbt, and coursier") + lazy val githubWorkflowCustomWorkflows = settingKey[Map[String, Workflow]]("Custom workflows, defined by a map of file name to Workflow.") + lazy val githubWorkflowSbtCommand = settingKey[String]("The command which invokes sbt (default: sbt)") lazy val githubWorkflowUseSbtThinClient = settingKey[Boolean]("Whether to use sbt's native thin client (default for sbt >= 1.4: true, default for sbt < 1.4: false)") diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index b345545..3fdbd61 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -393,13 +393,9 @@ ${indent(job.steps.map(compileStep(_, sbt, declareShell = declareShell)).mkStrin s"${job.id}:\n${indent(body, 1)}" } - def compileWorkflow( - workflow: Workflow, - env: Map[String, String], - sbt: String) - : String = { + def compileWorkflow(workflow: Workflow, sbt: String): String = { - val renderedEnvPre = compileEnv(env) + val renderedEnvPre = compileEnv(workflow.env) val renderedEnv = if (renderedEnvPre.isEmpty) "" else @@ -585,6 +581,8 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}""" githubWorkflowGeneratedCacheSteps.value.toList }, + githubWorkflowCustomWorkflows := Map.empty, + githubWorkflowGeneratedCI := { val publicationCondPre = githubWorkflowPublishTargetBranches.value.map(compileBranchPredicate("github.ref", _)).mkString("(", " || ", ")") @@ -654,10 +652,10 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}""" githubWorkflowTargetTags.value.toList, githubWorkflowPREventTypes.value.toList ) + ), + githubWorkflowGeneratedCI.value.toList, + githubWorkflowEnv.value, ), - githubWorkflowGeneratedCI.value.toList, - ), - githubWorkflowEnv.value, sbt) } @@ -670,6 +668,21 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}""" } } + private val customWorkflowContents = Def task { + val sbt = if (githubWorkflowUseSbtThinClient.value) { + githubWorkflowSbtCommand.value + " --client" + } else { + githubWorkflowSbtCommand.value + } + + githubWorkflowCustomWorkflows.value.map { + case (file, workflow) if file.endsWith(".yml") || file.endsWith(".yaml") => + file -> compileWorkflow(workflow, sbt) + case (file, workflow) => + (file + ".yml") -> compileWorkflow(workflow, sbt) + } + } + private val workflowsDirTask = Def task { val githubDir = baseDirectory.value / ".github" val workflowsDir = githubDir / "workflows" @@ -724,6 +737,15 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}""" } finally { cleanWriter.close() } + + customWorkflowContents.value.foreach{ case (file, content) => + val writer = new BufferedWriter(new FileWriter(file)) + try{ + writer.write(content) + } finally { + writer.close() + } + } }, githubWorkflowCheck := { diff --git a/src/main/scala/sbtghactions/Workflow.scala b/src/main/scala/sbtghactions/Workflow.scala index f027915..bb26aa0 100644 --- a/src/main/scala/sbtghactions/Workflow.scala +++ b/src/main/scala/sbtghactions/Workflow.scala @@ -18,7 +18,11 @@ package sbtghactions import sbtghactions.RenderFunctions.{indentOnce, wrap} -final case class Workflow(name: String, ons: Seq[TriggerEvent], jobs: Seq[WorkflowJob]) { +final case class Workflow( + name: String, + ons: Seq[TriggerEvent], + jobs: Seq[WorkflowJob], + env: Map[String, String]) { def render: String = s"""|name: ${wrap(name)} diff --git a/src/test/scala/sbtghactions/GenerativePluginSpec.scala b/src/test/scala/sbtghactions/GenerativePluginSpec.scala index 049f97b..d68d2c4 100644 --- a/src/test/scala/sbtghactions/GenerativePluginSpec.scala +++ b/src/test/scala/sbtghactions/GenerativePluginSpec.scala @@ -51,10 +51,10 @@ class GenerativePluginSpec extends Specification { List( WebhookEvent.PullRequest(List("main"), Nil, PREventType.Defaults), WebhookEvent.Push(List("main"), Nil) + ), + Nil, + Map(), ), - Nil - ), - Map(), "sbt") mustEqual expected } @@ -78,10 +78,10 @@ class GenerativePluginSpec extends Specification { List( WebhookEvent.PullRequest(List("main"), Nil, PREventType.Defaults), WebhookEvent.Push(List("main"), List("howdy")) + ), + Nil, + Map(), ), - Nil - ), - Map(), "sbt") mustEqual expected } @@ -108,9 +108,9 @@ class GenerativePluginSpec extends Specification { Nil, List(PREventType.ReadyForReview, PREventType.ReviewRequested, PREventType.Opened)), WebhookEvent.Push(List("main"), Nil)), - Nil + Nil, + Map(), ), - Map(), "sbt") mustEqual expected } @@ -150,10 +150,10 @@ class GenerativePluginSpec extends Specification { WorkflowJob( "build", "Build and Test", - List(WorkflowStep.Run(List("echo Hello World"))))) + List(WorkflowStep.Run(List("echo Hello World"))))), + Map( + "GITHUB_TOKEN" -> s"$${{ secrets.GITHUB_TOKEN }}"), ), - Map( - "GITHUB_TOKEN" -> s"$${{ secrets.GITHUB_TOKEN }}"), "sbt") mustEqual expected } @@ -197,7 +197,7 @@ class GenerativePluginSpec extends Specification { List( WebhookEvent.PullRequest(List("main"), Nil, PREventType.Defaults), WebhookEvent.Push(List("main"), Nil) - ), + ), List( WorkflowJob( "build", @@ -207,9 +207,9 @@ class GenerativePluginSpec extends Specification { WorkflowJob( "what", "If we just didn't", - List(WorkflowStep.Run(List("whoami"))))) + List(WorkflowStep.Run(List("whoami"))))), + Map(), ), - Map(), "") mustEqual expected } @@ -242,16 +242,16 @@ class GenerativePluginSpec extends Specification { List( WebhookEvent.PullRequest(List("main"), Nil, PREventType.Defaults), WebhookEvent.Push(List("main"), Nil) - ), + ), List( WorkflowJob( "build", "Build and Test", List(WorkflowStep.Run(List("echo yikes"))), container = Some( - JobContainer("not:real-thing")))) + JobContainer("not:real-thing")))), + Map(), ), - Map(), "") mustEqual expected } @@ -294,7 +294,7 @@ class GenerativePluginSpec extends Specification { List( WebhookEvent.PullRequest(List("main"), Nil, PREventType.Defaults), WebhookEvent.Push(List("main"), Nil) - ), + ), List( WorkflowJob( "build", @@ -307,9 +307,9 @@ class GenerativePluginSpec extends Specification { env = Map("VERSION" -> "1.0", "PATH" -> "/nope"), volumes = Map("/source" -> "/dest/ination"), ports = List(80, 443), - options = List("--cpus", "1"))))) + options = List("--cpus", "1"))))), + Map(), ), - Map(), "") mustEqual expected } } From c228a3d7aa84a030e915512771e642cf9276432b Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <987Nabil@users.noreply.github.com> Date: Mon, 7 Jun 2021 21:15:10 +0200 Subject: [PATCH 07/23] Do not use tags for pull request in ci workflow --- src/main/scala/sbtghactions/GenerativePlugin.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index 3fdbd61..92249cd 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -649,7 +649,7 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}""" ), WebhookEvent.PullRequest( githubWorkflowTargetBranches.value.toList, - githubWorkflowTargetTags.value.toList, + Nil, githubWorkflowPREventTypes.value.toList ) ), From 99c15d68e085ec066eae799fc71a893b7c6348c2 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <987Nabil@users.noreply.github.com> Date: Tue, 8 Jun 2021 02:24:07 +0200 Subject: [PATCH 08/23] Docu for custom workflows --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 81c160e..196b20f 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ ThisBuild / githubWorkflowPublish := Seq( ### Generative -- `githubWorkflowGenerate` – Generates (and overwrites if extant) **ci.yml** and **clean.yml** workflows according to configuration within sbt. The **clean.yml** workflow is something that GitHub Actions should just do by default: it removes old build artifacts to prevent them from running up your storage usage (it has no effect on currently running builds). This workflow is unconfigurable and is simply drawn from the static contents of the **clean.yml** resource file within this repository. +- `githubWorkflowGenerate` – Generates (and overwrites if extant) **ci.yml**, **clean.yml** and all custom (see `githubWorkflowCustomWorkflows`) workflows according to configuration within sbt. The **clean.yml** workflow is something that GitHub Actions should just do by default: it removes old build artifacts to prevent them from running up your storage usage (it has no effect on currently running builds). This workflow is unconfigurable and is simply drawn from the static contents of the **clean.yml** resource file within this repository. - `githubWorkflowCheck` – Checks to see if the **ci.yml** and **clean.yml** files are equivalent to what would be generated and errors if otherwise. This task is run from within the generated **ci.yml** to ensure that the build and the workflow are kept in sync. As a general rule, any time you change the workflow configuration within sbt, you should regenerate the **ci.yml** and commit the results, but inevitably people forget. This check fails the build if that happens. Note that if you *need* to manually fiddle with the **ci.yml** contents, for whatever reason, you will need to remove the call to this check from within the workflow, otherwise your build will simply fail. ## Settings @@ -104,6 +104,7 @@ Any and all settings which affect the behavior of the generative plugin should b - `githubWorkflowJobSetup` : `Seq[WorkflowStep]` – The automatically-generated checkout, setup, and cache steps which are common to all jobs which touch the build (default: autogenerated) - `githubWorkflowEnv` : `Map[String, String]` – An environment which is global to the entire **ci.yml** workflow. Defaults to `Map("GITHUB_TOKEN" -> "${{ secrets.GITHUB_TOKEN }}")` since it's so commonly needed. - `githubWorkflowAddedJobs` : `Seq[WorkflowJob]` – A convenience mechanism for adding extra custom jobs to the **ci.yml** workflow (though you can also do this by modifying `githubWorkflowGeneratedCI`). Defaults to empty. +- `githubWorkflowCustomWorkflows`: `Map[String, Workflow]` - This is the place to define your custom workflows. The key represents the filename (**.yml** is added if not provided) the value the workflow definition. The settings of the generative plugin apply, as long as they are not part of the workflow trigger events. For example, the `githubWorkflowTargetBranches` setting has no influence on custom workflows, but `githubWorkflowScalaVersions` does. #### `build` Job From c6f0a65e8f3aa28bd7f2a8a10f444d9515db25fd Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <987Nabil@users.noreply.github.com> Date: Tue, 8 Jun 2021 23:08:25 +0200 Subject: [PATCH 09/23] Paths for Push and PullRequest events --- .../scala/sbtghactions/GenerativePlugin.scala | 10 ++-- .../scala/sbtghactions/TriggerEvent.scala | 20 ++++--- src/main/scala/sbtghactions/WorkflowJob.scala | 6 +- .../sbtghactions/GenerativePluginSpec.scala | 27 +++++---- .../scala/sbtghactions/TriggerEventSpec.scala | 59 +++++++++++++------ 5 files changed, 80 insertions(+), 42 deletions(-) diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index 92249cd..d764017 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -645,11 +645,13 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}""" List( WebhookEvent.Push( githubWorkflowTargetBranches.value.toList, - githubWorkflowTargetTags.value.toList + githubWorkflowTargetTags.value.toList, + Nil ), WebhookEvent.PullRequest( githubWorkflowTargetBranches.value.toList, Nil, + Nil, githubWorkflowPREventTypes.value.toList ) ), @@ -738,9 +740,9 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}""" cleanWriter.close() } - customWorkflowContents.value.foreach{ case (file, content) => - val writer = new BufferedWriter(new FileWriter(file)) - try{ + customWorkflowContents.value.foreach { case (file, content) => + val writer = new BufferedWriter(new FileWriter(workflowsDirTask.value / file)) + try { writer.write(content) } finally { writer.close() diff --git a/src/main/scala/sbtghactions/TriggerEvent.scala b/src/main/scala/sbtghactions/TriggerEvent.scala index f967e06..483af7e 100644 --- a/src/main/scala/sbtghactions/TriggerEvent.scala +++ b/src/main/scala/sbtghactions/TriggerEvent.scala @@ -115,15 +115,19 @@ object WebhookEvent { case object Public extends PlainNameEvent - final case class PullRequest(branches: Seq[String], tags: Seq[String], types: Seq[PREventType]) + final case class PullRequest( + branches: Seq[String], + tags: Seq[String], + paths: Seq[String], + types: Seq[PREventType]) extends WebhookEvent { override def render: String = s"$name:" + - indentOnce { renderBranches(branches) + renderTags + renderTypes } + indentOnce { renderBranches(branches) + renderTags + renderPaths + renderTypes } - private def renderTags = - renderParamWithList("tags", tags) + private def renderTags = renderParamWithList("tags", tags) + private def renderPaths: String = if (paths.isEmpty) "" else renderParamWithList("paths", paths) private def renderTypes = if (types == PREventType.Defaults) "" @@ -137,13 +141,15 @@ object WebhookEvent { final case class PullRequestTarget(types: Seq[PRTargetEventType]) extends TypedEvent - final case class Push(branches: Seq[String], tags: Seq[String]) extends WebhookEvent { + final case class Push(branches: Seq[String], tags: Seq[String], paths: Seq[String]) + extends WebhookEvent { override def render: String = s"$name:" + - indentOnce { renderBranches(branches) + renderTags } + indentOnce { renderBranches(branches) + renderTags + renderPaths } - def renderTags: String = if (tags.isEmpty) "" else renderParamWithList("tags", tags) + def renderTags: String = if (tags.isEmpty) "" else renderParamWithList("tags", tags) + def renderPaths: String = if (paths.isEmpty) "" else renderParamWithList("paths", paths) } diff --git a/src/main/scala/sbtghactions/WorkflowJob.scala b/src/main/scala/sbtghactions/WorkflowJob.scala index b9ba10b..1d6341b 100644 --- a/src/main/scala/sbtghactions/WorkflowJob.scala +++ b/src/main/scala/sbtghactions/WorkflowJob.scala @@ -32,4 +32,8 @@ final case class WorkflowJob( matrixExcs: List[MatrixExclude] = List(), runsOnExtraLabels: List[String] = List(), container: Option[JobContainer] = None, - environment: Option[JobEnvironment] = None) + environment: Option[JobEnvironment] = None) { + + def needsJob(job: WorkflowJob): WorkflowJob = + copy(needs = needs :+ job.id) +} diff --git a/src/test/scala/sbtghactions/GenerativePluginSpec.scala b/src/test/scala/sbtghactions/GenerativePluginSpec.scala index d68d2c4..69454b9 100644 --- a/src/test/scala/sbtghactions/GenerativePluginSpec.scala +++ b/src/test/scala/sbtghactions/GenerativePluginSpec.scala @@ -49,8 +49,8 @@ class GenerativePluginSpec extends Specification { Workflow( "test", List( - WebhookEvent.PullRequest(List("main"), Nil, PREventType.Defaults), - WebhookEvent.Push(List("main"), Nil) + WebhookEvent.PullRequest(List("main"), Nil, Nil, PREventType.Defaults), + WebhookEvent.Push(List("main"), Nil, Nil) ), Nil, Map(), @@ -76,8 +76,8 @@ class GenerativePluginSpec extends Specification { Workflow( "test", List( - WebhookEvent.PullRequest(List("main"), Nil, PREventType.Defaults), - WebhookEvent.Push(List("main"), List("howdy")) + WebhookEvent.PullRequest(List("main"), Nil, Nil, PREventType.Defaults), + WebhookEvent.Push(List("main"), List("howdy"), Nil) ), Nil, Map(), @@ -106,8 +106,9 @@ class GenerativePluginSpec extends Specification { WebhookEvent.PullRequest( List("main"), Nil, + Nil, List(PREventType.ReadyForReview, PREventType.ReviewRequested, PREventType.Opened)), - WebhookEvent.Push(List("main"), Nil)), + WebhookEvent.Push(List("main"), Nil, Nil)), Nil, Map(), ), @@ -143,8 +144,8 @@ class GenerativePluginSpec extends Specification { Workflow( "test2", List( - WebhookEvent.PullRequest(List("main", "backport/v*"), Nil, PREventType.Defaults), - WebhookEvent.Push(List("main", "backport/v*"), Nil) + WebhookEvent.PullRequest(List("main", "backport/v*"), Nil, Nil, PREventType.Defaults), + WebhookEvent.Push(List("main", "backport/v*"), Nil, Nil) ), List( WorkflowJob( @@ -195,8 +196,8 @@ class GenerativePluginSpec extends Specification { Workflow( "test3", List( - WebhookEvent.PullRequest(List("main"), Nil, PREventType.Defaults), - WebhookEvent.Push(List("main"), Nil) + WebhookEvent.PullRequest(List("main"), Nil, Nil, PREventType.Defaults), + WebhookEvent.Push(List("main"), Nil, Nil) ), List( WorkflowJob( @@ -240,8 +241,8 @@ class GenerativePluginSpec extends Specification { Workflow( "test4", List( - WebhookEvent.PullRequest(List("main"), Nil, PREventType.Defaults), - WebhookEvent.Push(List("main"), Nil) + WebhookEvent.PullRequest(List("main"), Nil, Nil, PREventType.Defaults), + WebhookEvent.Push(List("main"), Nil, Nil) ), List( WorkflowJob( @@ -292,8 +293,8 @@ class GenerativePluginSpec extends Specification { Workflow( "test4", List( - WebhookEvent.PullRequest(List("main"), Nil, PREventType.Defaults), - WebhookEvent.Push(List("main"), Nil) + WebhookEvent.PullRequest(List("main"), Nil, Nil, PREventType.Defaults), + WebhookEvent.Push(List("main"), Nil, Nil) ), List( WorkflowJob( diff --git a/src/test/scala/sbtghactions/TriggerEventSpec.scala b/src/test/scala/sbtghactions/TriggerEventSpec.scala index 89e8025..ae01bc7 100644 --- a/src/test/scala/sbtghactions/TriggerEventSpec.scala +++ b/src/test/scala/sbtghactions/TriggerEventSpec.scala @@ -68,27 +68,33 @@ class TriggerEventSpec extends Specification with AllExpectations { } "pull request" should { - "render without branches, tags or types" in { + "render without branches, tags, paths or types" in { val expected = "pull_request:" - WebhookEvent.PullRequest(Nil, Nil, Nil).render mustEqual expected + WebhookEvent.PullRequest(Nil, Nil, Nil, Nil).render mustEqual expected } - "render without branches, tags, but with types" in { + "render without branches, tags, paths, but with types" in { val expected = """|pull_request: | types: [edited, ready_for_review]""".stripMargin WebhookEvent - .PullRequest(Nil, Nil, List(PREventType.Edited, PREventType.ReadyForReview)) + .PullRequest(Nil, Nil, Nil, List(PREventType.Edited, PREventType.ReadyForReview)) .render mustEqual expected } - "render without branches, but with tags and types" in { + "render without branches, but with tags, paths and types" in { val expected = """|pull_request: | tags: [v1*, v2*] + | paths: [src/main/**] | types: [edited, ready_for_review]""".stripMargin WebhookEvent - .PullRequest(Nil, List("v1*", "v2*"), List(PREventType.Edited, PREventType.ReadyForReview)) + .PullRequest( + Nil, + List("v1*", "v2*"), + List("src/main/**"), + List(PREventType.Edited, PREventType.ReadyForReview) + ) .render mustEqual expected } @@ -97,22 +103,27 @@ class TriggerEventSpec extends Specification with AllExpectations { """|pull_request: | branches: [master] | tags: [v1*, v2*] + | paths: [src/main/**] | types: [edited, ready_for_review]""".stripMargin WebhookEvent .PullRequest( List("master"), List("v1*", "v2*"), + List("src/main/**"), List(PREventType.Edited, PREventType.ReadyForReview) ) .render mustEqual expected } - "render without types, but with branches and tags" in { + "render without types, but with branches, tags and paths" in { val expected = """|pull_request: | branches: [master] - | tags: [v1*, v2*]""".stripMargin - WebhookEvent.PullRequest(List("master"), List("v1*", "v2*"), Nil).render mustEqual expected + | tags: [v1*, v2*] + | paths: [src/main/**]""".stripMargin + WebhookEvent + .PullRequest(List("master"), List("v1*", "v2*"), List("src/main/**"), Nil) + .render mustEqual expected } "render without tags, but with branches and types" in { @@ -121,22 +132,36 @@ class TriggerEventSpec extends Specification with AllExpectations { | branches: [master] | types: [edited, ready_for_review]""".stripMargin WebhookEvent - .PullRequest(List("master"), Nil, List(PREventType.Edited, PREventType.ReadyForReview)) + .PullRequest(List("master"), Nil, Nil, List(PREventType.Edited, PREventType.ReadyForReview)) .render mustEqual expected } + "render only with paths" in { + val expected = + """|push: + | paths: [src/main/**]""".stripMargin + WebhookEvent.PullRequest(Nil, Nil, List("src/main/**"), Nil).render mustEqual expected + } } "push" should { - "render without branches, tags or types" in { + "render without branches, tags or paths" in { val expected = "push:" - WebhookEvent.Push(Nil, Nil).render mustEqual expected + WebhookEvent.Push(Nil, Nil, Nil).render mustEqual expected } - "render without branches, but with tags and types" in { + "render without branches, but with tags and paths" in { val expected = """|push: - | tags: [v1*, v2*]""".stripMargin - WebhookEvent.Push(Nil, List("v1*", "v2*")).render mustEqual expected + | tags: [v1*, v2*] + | paths: [src/main/**]""".stripMargin + WebhookEvent.Push(Nil, List("v1*", "v2*"), List("src/main/**")).render mustEqual expected + } + + "render only with paths" in { + val expected = + """|push: + | paths: [src/main/**]""".stripMargin + WebhookEvent.Push(Nil, Nil, List("src/main/**")).render mustEqual expected } "render with branches and tags" in { @@ -144,14 +169,14 @@ class TriggerEventSpec extends Specification with AllExpectations { """|push: | branches: [master] | tags: [v1*, v2*]""".stripMargin - WebhookEvent.Push(List("master"), List("v1*", "v2*")).render mustEqual expected + WebhookEvent.Push(List("master"), List("v1*", "v2*"), Nil).render mustEqual expected } "render without tags, but with branches" in { val expected = """|push: | branches: [master]""".stripMargin - WebhookEvent.Push(List("master"), Nil).render mustEqual expected + WebhookEvent.Push(List("master"), Nil, Nil).render mustEqual expected } } From 153bb2d7007264aaf08af2806d40c7a9d9a7e881 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <987Nabil@users.noreply.github.com> Date: Tue, 15 Jun 2021 01:40:08 +0200 Subject: [PATCH 10/23] Make Workflow jobs compile without oses The os field in build matrices are is not mandatory. On private action runners you may just want to provide custom labels. This is now possible. --- .../scala/sbtghactions/GenerativePlugin.scala | 7 ++++--- .../sbtghactions/GenerativePluginSpec.scala | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index d764017..989ed9c 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -373,17 +373,18 @@ ${indent(rendered.mkString("\n"), 1)}""" val declareShell = job.oses.exists(_.contains("windows")) + val matrixOs = if(job.oses.isEmpty) Nil else List("\"${{ matrix.os }}\"") + val runsOn = if (job.runsOnExtraLabels.isEmpty) s"$${{ matrix.os }}" else - job.runsOnExtraLabels.mkString(s"""[ "$${{ matrix.os }}", """, ", ", " ]" ) + (matrixOs ++ job.runsOnExtraLabels).mkString(s"""[ """, ", ", " ]" ) val renderedFailFast = job.matrixFailFast.fold("")("\n fail-fast: " + _) val body = s"""name: ${wrap(job.name)}${renderedNeeds}${renderedCond} strategy:${renderedFailFast} - matrix: - os:${compileList(job.oses, 3)} + matrix:${if (job.oses.isEmpty) "" else s"\n os:${compileList(job.oses, 3)}"} scala:${compileList(job.scalas, 3)} java:${compileList(job.javas, 3)}${renderedMatrices} runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedEnv} diff --git a/src/test/scala/sbtghactions/GenerativePluginSpec.scala b/src/test/scala/sbtghactions/GenerativePluginSpec.scala index 69454b9..b84708a 100644 --- a/src/test/scala/sbtghactions/GenerativePluginSpec.scala +++ b/src/test/scala/sbtghactions/GenerativePluginSpec.scala @@ -631,6 +631,26 @@ class GenerativePluginSpec extends Specification { - run: echo hello""" } + "compile a job with extra runs-on labels, but without oses" in { + compileJob( + WorkflowJob( + "job", + "my-name", + List( + WorkflowStep.Run(List("echo hello"))), + runsOnExtraLabels = List("runner-label", "runner-group"), + oses = Nil + ), "") mustEqual """job: + name: my-name + strategy: + matrix: + scala: [2.13.4] + java: [adopt@1.8] + runs-on: [ runner-label, runner-group ] + steps: + - run: echo hello""" + } + "produce an error when compiling a job with `include` key in matrix" in { compileJob( WorkflowJob( From 33d6ee70d79528d05f44324c3a38c813bad37be7 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <987Nabil@users.noreply.github.com> Date: Sun, 20 Jun 2021 09:09:18 +0200 Subject: [PATCH 11/23] Introduce generation targets to adjust which workflows will be generated When having custom workflows, one might not want to have ci.yml or clean.yml generated. This is now configurable via githubWorkflowGenerationTargets --- README.md | 1 + .../scala/sbtghactions/GenerationTarget.scala | 11 +++++ .../scala/sbtghactions/GenerativeKeys.scala | 1 + .../scala/sbtghactions/GenerativePlugin.scala | 46 ++++++++++++------- 4 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 src/main/scala/sbtghactions/GenerationTarget.scala diff --git a/README.md b/README.md index 196b20f..0840404 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ Any and all settings which affect the behavior of the generative plugin should b #### General +- `githubWorkflowGenerationTargets` : `Set[GenerationTarget]` — A set of targets to be generated. Possible values are `CI`, `Clean` and `Custom`. - `githubWorkflowGeneratedCI` : `Seq[WorkflowJob]` — Contains a description of the **ci.yml** jobs that will drive the generation if used. This setting can be overridden to customize the jobs (e.g. by adding additional jobs to the workflow). - `githubWorkflowGeneratedUploadSteps` : `Seq[WorkflowStep]` – Contains a list of steps which are used to upload generated intermediate artifacts from the `build` job. This is mostly for reference and introspection purposes; one would not be expected to *change* this setting. - `githubWorkflowGeneratedDownloadSteps` : `Seq[WorkflowStep]` – Contains a list of steps which are used to download generated intermediate artifacts from the `build` job. This is mostly for reference and introspection purposes; one would not be expected to *change* this setting. This setting is particularly useful in conjunction with `githubWorkflowAddedJobs`: if you're adding a job which needs access to intermediate artifacts, you should make sure these steps are part of the process. diff --git a/src/main/scala/sbtghactions/GenerationTarget.scala b/src/main/scala/sbtghactions/GenerationTarget.scala new file mode 100644 index 0000000..f8fc6a4 --- /dev/null +++ b/src/main/scala/sbtghactions/GenerationTarget.scala @@ -0,0 +1,11 @@ +package sbtghactions + +sealed trait GenerationTarget extends Product with Serializable + +object GenerationTarget { + val all: Set[GenerationTarget] = Set(GenerationTarget.CI, GenerationTarget.Clean, GenerationTarget.Custom) + + case object CI extends GenerationTarget + case object Clean extends GenerationTarget + case object Custom extends GenerationTarget +} diff --git a/src/main/scala/sbtghactions/GenerativeKeys.scala b/src/main/scala/sbtghactions/GenerativeKeys.scala index aca8958..1cb0d60 100644 --- a/src/main/scala/sbtghactions/GenerativeKeys.scala +++ b/src/main/scala/sbtghactions/GenerativeKeys.scala @@ -23,6 +23,7 @@ trait GenerativeKeys { lazy val githubWorkflowGenerate = taskKey[Unit]("Generates (and overwrites if extant) a ci.yml and clean.yml actions description according to configuration") lazy val githubWorkflowCheck = taskKey[Unit]("Checks to see if the ci.yml and clean.yml files are equivalent to what would be generated and errors if otherwise") + lazy val githubWorkflowGenerationTargets = settingKey[Set[GenerationTarget]]("Configure targets to be generated. (CI, Clean or Custom)") lazy val githubWorkflowGeneratedCI = settingKey[Seq[WorkflowJob]]("The sequence of jobs which will make up the generated ci workflow (ci.yml)") lazy val githubWorkflowGeneratedUploadSteps = settingKey[Seq[WorkflowStep]]("The sequence of steps used to upload intermediate build artifacts for an adjacent job") lazy val githubWorkflowGeneratedDownloadSteps = settingKey[Seq[WorkflowStep]]("The sequence of steps used to download intermediate build artifacts published by an adjacent job") diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index 989ed9c..0bc2343 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -16,8 +16,8 @@ package sbtghactions -import sbt._ import sbt.Keys._ +import sbt._ import java.io.{BufferedWriter, FileWriter} import java.nio.file.FileSystems @@ -583,6 +583,7 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}""" }, githubWorkflowCustomWorkflows := Map.empty, + githubWorkflowGenerationTargets := GenerationTarget.all, githubWorkflowGeneratedCI := { val publicationCondPre = @@ -727,26 +728,39 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}""" val ciYml = ciYmlFile.value val cleanYml = cleanYmlFile.value - val ciWriter = new BufferedWriter(new FileWriter(ciYml)) - try { - ciWriter.write(ciContents) - } finally { - ciWriter.close() - } + val targets = githubWorkflowGenerationTargets.value + + val workflowDir = workflowsDirTask.value + val workflowContents = customWorkflowContents.value - val cleanWriter = new BufferedWriter(new FileWriter(cleanYml)) - try { - cleanWriter.write(cleanContents) - } finally { - cleanWriter.close() + + + if (targets(GenerationTarget.CI)) { + val ciWriter = new BufferedWriter(new FileWriter(ciYml)) + try { + ciWriter.write(ciContents) + } finally { + ciWriter.close() + } } - customWorkflowContents.value.foreach { case (file, content) => - val writer = new BufferedWriter(new FileWriter(workflowsDirTask.value / file)) + if (targets(GenerationTarget.Clean)) { + val cleanWriter = new BufferedWriter(new FileWriter(cleanYml)) try { - writer.write(content) + cleanWriter.write(cleanContents) } finally { - writer.close() + cleanWriter.close() + } + } + + if (targets(GenerationTarget.Custom)) { + workflowContents.foreach { case (file, content) => + val writer = new BufferedWriter(new FileWriter(workflowDir / file)) + try { + writer.write(content) + } finally { + writer.close() + } } } }, From 0ca1ced081d28ba02f843a1a49f8f52d20948e58 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <987Nabil@users.noreply.github.com> Date: Thu, 12 Aug 2021 22:37:02 +0200 Subject: [PATCH 12/23] Fix test expectation for path only rendering of pull request events --- src/test/scala/sbtghactions/TriggerEventSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/scala/sbtghactions/TriggerEventSpec.scala b/src/test/scala/sbtghactions/TriggerEventSpec.scala index ae01bc7..c7865e5 100644 --- a/src/test/scala/sbtghactions/TriggerEventSpec.scala +++ b/src/test/scala/sbtghactions/TriggerEventSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -137,7 +137,7 @@ class TriggerEventSpec extends Specification with AllExpectations { } "render only with paths" in { val expected = - """|push: + """|pull_request: | paths: [src/main/**]""".stripMargin WebhookEvent.PullRequest(Nil, Nil, List("src/main/**"), Nil).render mustEqual expected } From f7d7603d1457cc42bf46a5683683184bbf26c410 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <987Nabil@users.noreply.github.com> Date: Wed, 29 Dec 2021 06:29:28 +0100 Subject: [PATCH 13/23] Fix rendering in case of empty scalas --- src/main/scala/sbtghactions/GenerativePlugin.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index 15bc694..80d12b9 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -384,8 +384,7 @@ ${indent(rendered.mkString("\n"), 1)}""" val body = s"""name: ${wrap(job.name)}${renderedNeeds}${renderedCond} strategy:${renderedFailFast} - matrix:${if (job.oses.isEmpty) "" else s"\n os:${compileList(job.oses, 3)}"} - scala:${compileList(job.scalas, 3)} + matrix:${if (job.oses.isEmpty) "" else s"\n os:${compileList(job.oses, 3)}"} ${if (job.scalas.nonEmpty) s"\n scala:${compileList(job.scalas, 3)}" else ""} java:${compileList(job.javas, 3)}${renderedMatrices} runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedEnv} steps: From c46d1cb719e081dce55a137b9a74d14a86d233ea Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> Date: Tue, 11 Jan 2022 07:41:47 +0100 Subject: [PATCH 14/23] Support rendering without strategy/matrix --- .../scala/sbtghactions/GenerativePlugin.scala | 32 +++++++++++---- .../sbtghactions/GenerativePluginSpec.scala | 39 +++++++++++++++++++ 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index 80d12b9..adfdda6 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -181,7 +181,7 @@ s"""$prefix: ${indent(rendered.mkString("\n"), 1)}""" } - def compileStep(step: WorkflowStep, sbt: String, declareShell: Boolean = false): String = { + def compileStep(step: WorkflowStep, sbt: String, declareShell: Boolean = false, scalaMatrixBuild: Boolean = true): String = { import WorkflowStep._ val renderedName = step.name.map(wrap).map("name: " + _ + "\n").getOrElse("") @@ -207,7 +207,7 @@ ${indent(rendered.mkString("\n"), 1)}""" renderedShell + "run: " + wrap(commands.mkString("\n")) case Sbt(commands, _, _, _, _) => - val version = "++${{ matrix.scala }}" + val version = if (scalaMatrixBuild) "++${{ matrix.scala }}" else "" val sbtClientMode = sbt.matches("""sbt.* --client($| .*)""") val safeCommands = if (sbtClientMode) s"'${(version :: commands).mkString("; ")}'" @@ -382,13 +382,31 @@ ${indent(rendered.mkString("\n"), 1)}""" val renderedFailFast = job.matrixFailFast.fold("")("\n fail-fast: " + _) - val body = s"""name: ${wrap(job.name)}${renderedNeeds}${renderedCond} -strategy:${renderedFailFast} - matrix:${if (job.oses.isEmpty) "" else s"\n os:${compileList(job.oses, 3)}"} ${if (job.scalas.nonEmpty) s"\n scala:${compileList(job.scalas, 3)}" else ""} - java:${compileList(job.javas, 3)}${renderedMatrices} + val renderedOses = + if (job.oses.isEmpty) "" else s"\n os:${compileList(job.oses, 3)}" + val renderedScalas = + if (job.scalas.isEmpty) "" else s"\n scala:${compileList(job.scalas, 3)}" + val renderedJavas = + if (job.javas.isEmpty) "" else s"\n java:${compileList(job.javas, 3)}" + val renderedMatrix = + if(renderedOses.nonEmpty || renderedScalas.nonEmpty || renderedJavas.nonEmpty) { + s"\n matrix:$renderedOses$renderedScalas$renderedJavas$renderedMatrices" + } else { + "" + } + + val renderedStrategy = + if(renderedFailFast.nonEmpty || renderedMatrix.nonEmpty) { + s"\nstrategy:$renderedFailFast$renderedMatrix" + } else { + "" + } + + val body = s"""name: ${wrap(job.name)}${renderedNeeds}${renderedCond}${renderedStrategy} runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedEnv} steps: -${indent(job.steps.map(compileStep(_, sbt, declareShell = declareShell)).mkString("\n\n"), 1)}""" +${indent( + job.steps.map(compileStep(_, sbt, declareShell = declareShell, scalaMatrixBuild = job.scalas.nonEmpty)).mkString("\n\n"), 1)}""" s"${job.id}:\n${indent(body, 1)}" } diff --git a/src/test/scala/sbtghactions/GenerativePluginSpec.scala b/src/test/scala/sbtghactions/GenerativePluginSpec.scala index 83e049e..ee59b4f 100644 --- a/src/test/scala/sbtghactions/GenerativePluginSpec.scala +++ b/src/test/scala/sbtghactions/GenerativePluginSpec.scala @@ -115,6 +115,45 @@ class GenerativePluginSpec extends Specification { "sbt") mustEqual expected } + "render a job without a strategy" in { + val expected = header + """ + |name: test2 + | + |on: + | push: + | branches: [main] + | + |env: + | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + | + |jobs: + | build: + | name: Build and Test + | runs-on: [ runner-label ] + | steps: + | - run: echo Hello World""".stripMargin + + compileWorkflow( + Workflow( + "test2", + List(WebhookEvent.Push(List("main"), Nil, Nil)), + List( + WorkflowJob( + "build", + "Build and Test", + List(WorkflowStep.Run(List("echo Hello World"))), + scalas = Nil, + javas= Nil, + oses = Nil, + runsOnExtraLabels = List("runner-label") + ) + ), + Map( + "GITHUB_TOKEN" -> s"$${{ secrets.GITHUB_TOKEN }}"), + ), + "sbt") mustEqual expected + } + "compile a one-job workflow targeting multiple branch patterns with a environment variables" in { val expected = header + s""" |name: test2 From 85702f8574a1b2a9fb84e8acc9fb0434f255149c Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> Date: Tue, 11 Jan 2022 11:06:08 +0100 Subject: [PATCH 15/23] Migrate custom workflows with main changes --- .../sbtghactions/CheckRunEventType.scala | 2 +- .../sbtghactions/CheckSuiteEventType.scala | 2 +- src/main/scala/sbtghactions/EventType.scala | 2 +- .../scala/sbtghactions/GenerationTarget.scala | 16 ++++ .../scala/sbtghactions/GenerativePlugin.scala | 29 +++---- .../sbtghactions/IssueCommentEventType.scala | 2 +- .../scala/sbtghactions/IssuesEventType.scala | 2 +- .../scala/sbtghactions/LabelEventType.scala | 2 +- .../sbtghactions/MilestoneEventType.scala | 2 +- .../PRReviewCommentEventType.scala | 2 +- .../sbtghactions/PRReviewEventType.scala | 2 +- .../sbtghactions/PRTargetEventType.scala | 2 +- .../sbtghactions/ProjectCardEventType.scala | 2 +- .../sbtghactions/ProjectColumnEventType.scala | 2 +- .../scala/sbtghactions/ProjectEventType.scala | 2 +- .../RegistryPackageEventType.scala | 2 +- .../scala/sbtghactions/ReleaseEventType.scala | 2 +- .../scala/sbtghactions/RenderFunctions.scala | 2 +- .../scala/sbtghactions/TriggerEvent.scala | 22 ++++-- .../scala/sbtghactions/WatchEventType.scala | 2 +- src/main/scala/sbtghactions/Workflow.scala | 2 +- .../sbtghactions/WorkflowRunEventType.scala | 2 +- .../sbtghactions/GenerativePluginSpec.scala | 78 ++++++++++++++----- .../scala/sbtghactions/TriggerEventSpec.scala | 26 +++---- 24 files changed, 130 insertions(+), 79 deletions(-) diff --git a/src/main/scala/sbtghactions/CheckRunEventType.scala b/src/main/scala/sbtghactions/CheckRunEventType.scala index 2edd5e4..43ce5ad 100644 --- a/src/main/scala/sbtghactions/CheckRunEventType.scala +++ b/src/main/scala/sbtghactions/CheckRunEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/CheckSuiteEventType.scala b/src/main/scala/sbtghactions/CheckSuiteEventType.scala index dde727b..9757d74 100644 --- a/src/main/scala/sbtghactions/CheckSuiteEventType.scala +++ b/src/main/scala/sbtghactions/CheckSuiteEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/EventType.scala b/src/main/scala/sbtghactions/EventType.scala index 0482a55..b7cb927 100644 --- a/src/main/scala/sbtghactions/EventType.scala +++ b/src/main/scala/sbtghactions/EventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/GenerationTarget.scala b/src/main/scala/sbtghactions/GenerationTarget.scala index f8fc6a4..f51e4b0 100644 --- a/src/main/scala/sbtghactions/GenerationTarget.scala +++ b/src/main/scala/sbtghactions/GenerationTarget.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2020-2021 Daniel Spiewak + * + * 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 sbtghactions sealed trait GenerationTarget extends Product with Serializable diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index 7d0b30a..ba4acd2 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -441,15 +441,6 @@ ${indent( renderedEnvPre + "\n\n" - val renderedPaths = paths match { - case Paths.None => - "" - case Paths.Include(paths) => - "\n" + indent(s"""paths: [${paths.map(wrap).mkString(", ")}]""", 2) - case Paths.Ignore(paths) => - "\n" + indent(s"""paths-ignore: [${paths.map(wrap).mkString(", ")}]""", 2) - } - s"""# This file was automatically generated by sbt-github-actions using the # githubWorkflowGenerate task. You should add and commit this file to # your git repository. It goes without saying that you shouldn't edit @@ -693,12 +684,12 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)} WebhookEvent.Push( githubWorkflowTargetBranches.value.toList, githubWorkflowTargetTags.value.toList, - Nil + githubWorkflowTargetPaths.value ), WebhookEvent.PullRequest( githubWorkflowTargetBranches.value.toList, Nil, - Nil, + githubWorkflowTargetPaths.value, githubWorkflowPREventTypes.value.toList ) ), @@ -771,6 +762,10 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)} val includeClean = githubWorkflowIncludeClean.value val cleanContents = readCleanContents.value + val targets = githubWorkflowGenerationTargets.value + val workflowContents = customWorkflowContents.value + val workflowsDir = workflowsDirTask.value + val ciYml = ciYmlFile.value val cleanYml = cleanYmlFile.value @@ -783,15 +778,9 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)} } if (targets(GenerationTarget.Custom)) { - workflowContents.foreach { case (file, content) => - val writer = new BufferedWriter(new FileWriter(workflowDir / file)) - try { - writer.write(content) - } finally { - writer.close() - } - } - }, + workflowContents.foreach { case (file, content) => IO.write(workflowsDir / file, content) } + } + }, githubWorkflowCheck := { val expectedCiContents = generateCiContents.value diff --git a/src/main/scala/sbtghactions/IssueCommentEventType.scala b/src/main/scala/sbtghactions/IssueCommentEventType.scala index f64829d..2d72e98 100644 --- a/src/main/scala/sbtghactions/IssueCommentEventType.scala +++ b/src/main/scala/sbtghactions/IssueCommentEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/IssuesEventType.scala b/src/main/scala/sbtghactions/IssuesEventType.scala index d41362c..5832e71 100644 --- a/src/main/scala/sbtghactions/IssuesEventType.scala +++ b/src/main/scala/sbtghactions/IssuesEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/LabelEventType.scala b/src/main/scala/sbtghactions/LabelEventType.scala index 1f86134..df40560 100644 --- a/src/main/scala/sbtghactions/LabelEventType.scala +++ b/src/main/scala/sbtghactions/LabelEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/MilestoneEventType.scala b/src/main/scala/sbtghactions/MilestoneEventType.scala index 59e93cd..923172b 100644 --- a/src/main/scala/sbtghactions/MilestoneEventType.scala +++ b/src/main/scala/sbtghactions/MilestoneEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/PRReviewCommentEventType.scala b/src/main/scala/sbtghactions/PRReviewCommentEventType.scala index f47613a..beb18e7 100644 --- a/src/main/scala/sbtghactions/PRReviewCommentEventType.scala +++ b/src/main/scala/sbtghactions/PRReviewCommentEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/PRReviewEventType.scala b/src/main/scala/sbtghactions/PRReviewEventType.scala index 3e2bbb4..568e796 100644 --- a/src/main/scala/sbtghactions/PRReviewEventType.scala +++ b/src/main/scala/sbtghactions/PRReviewEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/PRTargetEventType.scala b/src/main/scala/sbtghactions/PRTargetEventType.scala index 3d7f742..caa149d 100644 --- a/src/main/scala/sbtghactions/PRTargetEventType.scala +++ b/src/main/scala/sbtghactions/PRTargetEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/ProjectCardEventType.scala b/src/main/scala/sbtghactions/ProjectCardEventType.scala index 42d4faf..61da9e9 100644 --- a/src/main/scala/sbtghactions/ProjectCardEventType.scala +++ b/src/main/scala/sbtghactions/ProjectCardEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/ProjectColumnEventType.scala b/src/main/scala/sbtghactions/ProjectColumnEventType.scala index 231ef0e..409b0b7 100644 --- a/src/main/scala/sbtghactions/ProjectColumnEventType.scala +++ b/src/main/scala/sbtghactions/ProjectColumnEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/ProjectEventType.scala b/src/main/scala/sbtghactions/ProjectEventType.scala index 2a80f7d..ca02251 100644 --- a/src/main/scala/sbtghactions/ProjectEventType.scala +++ b/src/main/scala/sbtghactions/ProjectEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/RegistryPackageEventType.scala b/src/main/scala/sbtghactions/RegistryPackageEventType.scala index 5d764c1..6fdeff0 100644 --- a/src/main/scala/sbtghactions/RegistryPackageEventType.scala +++ b/src/main/scala/sbtghactions/RegistryPackageEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/ReleaseEventType.scala b/src/main/scala/sbtghactions/ReleaseEventType.scala index dc0851a..5177977 100644 --- a/src/main/scala/sbtghactions/ReleaseEventType.scala +++ b/src/main/scala/sbtghactions/ReleaseEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/RenderFunctions.scala b/src/main/scala/sbtghactions/RenderFunctions.scala index 3208e03..3a4f4b3 100644 --- a/src/main/scala/sbtghactions/RenderFunctions.scala +++ b/src/main/scala/sbtghactions/RenderFunctions.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/TriggerEvent.scala b/src/main/scala/sbtghactions/TriggerEvent.scala index 483af7e..517fa95 100644 --- a/src/main/scala/sbtghactions/TriggerEvent.scala +++ b/src/main/scala/sbtghactions/TriggerEvent.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -118,7 +118,7 @@ object WebhookEvent { final case class PullRequest( branches: Seq[String], tags: Seq[String], - paths: Seq[String], + paths: Paths, types: Seq[PREventType]) extends WebhookEvent { @@ -127,7 +127,12 @@ object WebhookEvent { indentOnce { renderBranches(branches) + renderTags + renderPaths + renderTypes } private def renderTags = renderParamWithList("tags", tags) - private def renderPaths: String = if (paths.isEmpty) "" else renderParamWithList("paths", paths) + private def renderPaths: String = + paths match { + case Paths.None => "" + case Paths.Include(paths) => renderParamWithList("paths", paths) + case Paths.Ignore(paths) => renderParamWithList("paths-ignore", paths) + } private def renderTypes = if (types == PREventType.Defaults) "" @@ -141,15 +146,20 @@ object WebhookEvent { final case class PullRequestTarget(types: Seq[PRTargetEventType]) extends TypedEvent - final case class Push(branches: Seq[String], tags: Seq[String], paths: Seq[String]) + final case class Push(branches: Seq[String], tags: Seq[String], paths: Paths) extends WebhookEvent { override def render: String = s"$name:" + indentOnce { renderBranches(branches) + renderTags + renderPaths } - def renderTags: String = if (tags.isEmpty) "" else renderParamWithList("tags", tags) - def renderPaths: String = if (paths.isEmpty) "" else renderParamWithList("paths", paths) + private def renderTags: String = if (tags.isEmpty) "" else renderParamWithList("tags", tags) + private def renderPaths: String = + paths match { + case Paths.None => "" + case Paths.Include(paths) => renderParamWithList("paths", paths) + case Paths.Ignore(paths) => renderParamWithList("paths-ignore", paths) + } } diff --git a/src/main/scala/sbtghactions/WatchEventType.scala b/src/main/scala/sbtghactions/WatchEventType.scala index e5e4fa8..6fbb8e8 100644 --- a/src/main/scala/sbtghactions/WatchEventType.scala +++ b/src/main/scala/sbtghactions/WatchEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/Workflow.scala b/src/main/scala/sbtghactions/Workflow.scala index bb26aa0..aa7c9ca 100644 --- a/src/main/scala/sbtghactions/Workflow.scala +++ b/src/main/scala/sbtghactions/Workflow.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/sbtghactions/WorkflowRunEventType.scala b/src/main/scala/sbtghactions/WorkflowRunEventType.scala index 1eac3b9..c4e4ee1 100644 --- a/src/main/scala/sbtghactions/WorkflowRunEventType.scala +++ b/src/main/scala/sbtghactions/WorkflowRunEventType.scala @@ -1,5 +1,5 @@ /* - * Copyright 2020 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/scala/sbtghactions/GenerativePluginSpec.scala b/src/test/scala/sbtghactions/GenerativePluginSpec.scala index 6e01c46..478eebb 100644 --- a/src/test/scala/sbtghactions/GenerativePluginSpec.scala +++ b/src/test/scala/sbtghactions/GenerativePluginSpec.scala @@ -50,8 +50,8 @@ class GenerativePluginSpec extends Specification { Workflow( "test", List( - WebhookEvent.PullRequest(List("main"), Nil, Nil, Paths.None, PREventType.Defaults), - WebhookEvent.Push(List("main"), Nil, Nil) + WebhookEvent.PullRequest(List("main"), Nil, Paths.None, PREventType.Defaults), + WebhookEvent.Push(List("main"), Nil, Paths.None) ), Nil, Map(), @@ -78,8 +78,8 @@ class GenerativePluginSpec extends Specification { Workflow( "test", List( - WebhookEvent.PullRequest(List("main"), Nil, Nil, Paths.None, PREventType.Defaults), - WebhookEvent.Push(List("main"), List("howdy"), Nil) + WebhookEvent.PullRequest(List("main"), Nil, Paths.None, PREventType.Defaults), + WebhookEvent.Push(List("main"), List("howdy"), Paths.None) ), Nil, Map(), @@ -109,9 +109,9 @@ class GenerativePluginSpec extends Specification { WebhookEvent.PullRequest( List("main"), Nil, - Nil, + Paths.None, List(PREventType.ReadyForReview, PREventType.ReviewRequested, PREventType.Opened)), - WebhookEvent.Push(List("main"), Nil, Nil)), + WebhookEvent.Push(List("main"), Nil, Paths.None)), Nil, Map(), ), @@ -134,12 +134,13 @@ class GenerativePluginSpec extends Specification { | name: Build and Test | runs-on: [ runner-label ] | steps: - | - run: echo Hello World""".stripMargin + | - run: echo Hello World + |""".stripMargin compileWorkflow( Workflow( "test2", - List(WebhookEvent.Push(List("main"), Nil, Nil)), + List(WebhookEvent.Push(List("main"), Nil, Paths.None)), List( WorkflowJob( "build", @@ -187,9 +188,8 @@ class GenerativePluginSpec extends Specification { Workflow( "test2", List( - WebhookEvent.PullRequest(List("main", "backport/v*"), Nil, Nil,Paths.None, - PREventType.Defaults), - WebhookEvent.Push(List("main", "backport/v*"), Nil, Nil) + WebhookEvent.PullRequest(List("main", "backport/v*"), Nil, Paths.None, PREventType.Defaults), + WebhookEvent.Push(List("main", "backport/v*"), Nil, Paths.None) ), List( WorkflowJob( @@ -241,8 +241,8 @@ class GenerativePluginSpec extends Specification { Workflow( "test3", List( - WebhookEvent.PullRequest(List("main"), Nil, Nil, PREventType.Defaults), - WebhookEvent.Push(List("main"), Nil, Nil) + WebhookEvent.PullRequest(List("main"), Nil, Paths.None, PREventType.Defaults), + WebhookEvent.Push(List("main"), Nil, Paths.None) ), List( WorkflowJob( @@ -287,8 +287,8 @@ class GenerativePluginSpec extends Specification { Workflow( "test4", List( - WebhookEvent.PullRequest(List("main"), Nil, Nil, PREventType.Defaults), - WebhookEvent.Push(List("main"), Nil, Nil) + WebhookEvent.PullRequest(List("main"), Nil, Paths.None, PREventType.Defaults), + WebhookEvent.Push(List("main"), Nil, Paths.None) ), List( WorkflowJob( @@ -340,8 +340,8 @@ class GenerativePluginSpec extends Specification { Workflow( "test4", List( - WebhookEvent.PullRequest(List("main"), Nil, Nil, PREventType.Defaults), - WebhookEvent.Push(List("main"), Nil, Nil) + WebhookEvent.PullRequest(List("main"), Nil, Paths.None, PREventType.Defaults), + WebhookEvent.Push(List("main"), Nil, Paths.None) ), List( WorkflowJob( @@ -377,7 +377,26 @@ class GenerativePluginSpec extends Specification { |${" " * 2} |""".stripMargin - compileWorkflow("test", List("main"), Nil, Paths.Include(List("**.scala", "**.sbt")), PREventType.Defaults, Map(), Nil, "sbt") mustEqual expected + compileWorkflow( + Workflow( + "test", + Seq( + WebhookEvent.PullRequest( + List("main"), + Nil, + Paths.Include(List("**.scala", "**.sbt")), + PREventType.Defaults + ), + WebhookEvent.Push( + List("main"), + Nil, + Paths.Include(List("**.scala", "**.sbt")) + ) + ), + Nil, + Map() + ), + "sbt") mustEqual expected } "render ignored paths on pull_request and push" in { @@ -396,7 +415,24 @@ class GenerativePluginSpec extends Specification { |${" " * 2} |""".stripMargin - compileWorkflow("test", List("main"), Nil, Paths.Ignore(List("docs/**")), PREventType.Defaults, Map(), Nil, "sbt") mustEqual expected + compileWorkflow( Workflow( + "test", + Seq( + WebhookEvent.PullRequest( + List("main"), + Nil, + Paths.Ignore(List("docs/**")), + PREventType.Defaults + ), + WebhookEvent.Push( + List("main"), + Nil, + Paths.Ignore(List("docs/**")) + ) + ), + Nil, + Map() + ), "sbt") mustEqual expected } } @@ -757,8 +793,8 @@ class GenerativePluginSpec extends Specification { name: my-name strategy: matrix: - scala: [2.13.4] - java: [adopt@1.8] + scala: [2.13.6] + java: [temurin@11] runs-on: [ runner-label, runner-group ] steps: - run: echo hello""" diff --git a/src/test/scala/sbtghactions/TriggerEventSpec.scala b/src/test/scala/sbtghactions/TriggerEventSpec.scala index c7865e5..0d54db0 100644 --- a/src/test/scala/sbtghactions/TriggerEventSpec.scala +++ b/src/test/scala/sbtghactions/TriggerEventSpec.scala @@ -1,5 +1,5 @@ /* - * Copyright 2021 Daniel Spiewak + * Copyright 2020-2021 Daniel Spiewak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,7 +70,7 @@ class TriggerEventSpec extends Specification with AllExpectations { "pull request" should { "render without branches, tags, paths or types" in { val expected = "pull_request:" - WebhookEvent.PullRequest(Nil, Nil, Nil, Nil).render mustEqual expected + WebhookEvent.PullRequest(Nil, Nil, Paths.None, Nil).render mustEqual expected } "render without branches, tags, paths, but with types" in { @@ -78,7 +78,7 @@ class TriggerEventSpec extends Specification with AllExpectations { """|pull_request: | types: [edited, ready_for_review]""".stripMargin WebhookEvent - .PullRequest(Nil, Nil, Nil, List(PREventType.Edited, PREventType.ReadyForReview)) + .PullRequest(Nil, Nil, Paths.None, List(PREventType.Edited, PREventType.ReadyForReview)) .render mustEqual expected } @@ -92,7 +92,7 @@ class TriggerEventSpec extends Specification with AllExpectations { .PullRequest( Nil, List("v1*", "v2*"), - List("src/main/**"), + Paths.Include(List("src/main/**")), List(PREventType.Edited, PREventType.ReadyForReview) ) .render mustEqual expected @@ -109,7 +109,7 @@ class TriggerEventSpec extends Specification with AllExpectations { .PullRequest( List("master"), List("v1*", "v2*"), - List("src/main/**"), + Paths.Include(List("src/main/**")), List(PREventType.Edited, PREventType.ReadyForReview) ) .render mustEqual expected @@ -122,7 +122,7 @@ class TriggerEventSpec extends Specification with AllExpectations { | tags: [v1*, v2*] | paths: [src/main/**]""".stripMargin WebhookEvent - .PullRequest(List("master"), List("v1*", "v2*"), List("src/main/**"), Nil) + .PullRequest(List("master"), List("v1*", "v2*"), Paths.Include(List("src/main/**")), Nil) .render mustEqual expected } @@ -132,21 +132,21 @@ class TriggerEventSpec extends Specification with AllExpectations { | branches: [master] | types: [edited, ready_for_review]""".stripMargin WebhookEvent - .PullRequest(List("master"), Nil, Nil, List(PREventType.Edited, PREventType.ReadyForReview)) + .PullRequest(List("master"), Nil, Paths.None, List(PREventType.Edited, PREventType.ReadyForReview)) .render mustEqual expected } "render only with paths" in { val expected = """|pull_request: | paths: [src/main/**]""".stripMargin - WebhookEvent.PullRequest(Nil, Nil, List("src/main/**"), Nil).render mustEqual expected + WebhookEvent.PullRequest(Nil, Nil, Paths.Include(List("src/main/**")), Nil).render mustEqual expected } } "push" should { "render without branches, tags or paths" in { val expected = "push:" - WebhookEvent.Push(Nil, Nil, Nil).render mustEqual expected + WebhookEvent.Push(Nil, Nil, Paths.None).render mustEqual expected } "render without branches, but with tags and paths" in { @@ -154,14 +154,14 @@ class TriggerEventSpec extends Specification with AllExpectations { """|push: | tags: [v1*, v2*] | paths: [src/main/**]""".stripMargin - WebhookEvent.Push(Nil, List("v1*", "v2*"), List("src/main/**")).render mustEqual expected + WebhookEvent.Push(Nil, List("v1*", "v2*"), Paths.Include(List("src/main/**"))).render mustEqual expected } "render only with paths" in { val expected = """|push: | paths: [src/main/**]""".stripMargin - WebhookEvent.Push(Nil, Nil, List("src/main/**")).render mustEqual expected + WebhookEvent.Push(Nil, Nil, Paths.Include(List("src/main/**"))).render mustEqual expected } "render with branches and tags" in { @@ -169,14 +169,14 @@ class TriggerEventSpec extends Specification with AllExpectations { """|push: | branches: [master] | tags: [v1*, v2*]""".stripMargin - WebhookEvent.Push(List("master"), List("v1*", "v2*"), Nil).render mustEqual expected + WebhookEvent.Push(List("master"), List("v1*", "v2*"), Paths.None).render mustEqual expected } "render without tags, but with branches" in { val expected = """|push: | branches: [master]""".stripMargin - WebhookEvent.Push(List("master"), Nil, Nil).render mustEqual expected + WebhookEvent.Push(List("master"), Nil, Paths.None).render mustEqual expected } } From 9c8bad76af5fa13ac8b2cdecc82e95b2a3c43f2b Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> Date: Tue, 11 Jan 2022 22:41:15 +0100 Subject: [PATCH 16/23] Support for reusable workflows --- .../scala/sbtghactions/GenerativePlugin.scala | 27 ++++++++++++++-- .../scala/sbtghactions/RenderFunctions.scala | 18 +++++++++++ src/main/scala/sbtghactions/WorkflowJob.scala | 3 +- src/main/scala/sbtghactions/WorkflowRef.scala | 30 ++++++++++++++++++ .../sbtghactions/GenerativePluginSpec.scala | 31 +++++++++++++++++++ 5 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 src/main/scala/sbtghactions/WorkflowRef.scala diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index ba4acd2..c1f1c4a 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -274,6 +274,10 @@ ${indent(rendered.mkString("\n"), 1)}""" def compileJob(job: WorkflowJob, sbt: String): String = { + //TODO better job data structure to avoid runtime errors + if(job.steps.nonEmpty && job.uses.nonEmpty) + sys.error(s"A job can either uses a reusable workflow or define steps. Please edit ${job.id}") + val renderedNeeds = if (job.needs.isEmpty) "" else @@ -423,11 +427,28 @@ ${indent(rendered.mkString("\n"), 1)}""" "" } + val renderedSteps = + if (job.steps.nonEmpty) { + s"""steps: + |${indent( + job.steps.map( + compileStep(_, sbt, declareShell = declareShell, scalaMatrixBuild = job.scalas.nonEmpty) + ).mkString("\n\n"), + 1) + }""".stripMargin + } else { + "" + } + + val renderedUses = + job.uses match { + case Some(value) => value.render + case None => "" + } + val body = s"""name: ${wrap(job.name)}${renderedNeeds}${renderedCond}${renderedStrategy} runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedEnv} -steps: -${indent( - job.steps.map(compileStep(_, sbt, declareShell = declareShell, scalaMatrixBuild = job.scalas.nonEmpty)).mkString("\n\n"), 1)}""" +$renderedSteps$renderedUses""" s"${job.id}:\n${indent(body, 1)}" } diff --git a/src/main/scala/sbtghactions/RenderFunctions.scala b/src/main/scala/sbtghactions/RenderFunctions.scala index 3a4f4b3..c9bb893 100644 --- a/src/main/scala/sbtghactions/RenderFunctions.scala +++ b/src/main/scala/sbtghactions/RenderFunctions.scala @@ -16,6 +16,8 @@ package sbtghactions +import sbtghactions.GenerativePlugin.{indent, isSafeString, wrap} + object RenderFunctions { def renderBranches(branches: Seq[String]): String = @@ -71,6 +73,22 @@ object RenderFunctions { else rendered.map("- " + _).map(indentOnce).mkString(s"\n$paramName:\n", "\n", "\n") } + def renderMap(env: Map[String, String], prefix: String): String = + if (env.isEmpty) { + "" + } else { + val rendered = env map { + case (key, value) => + if (!isSafeString(key) || key.indexOf(' ') >= 0) + sys.error(s"'$key' is not a valid variable name") + + s"""$key: ${wrap(value)}""" + } + s"""$prefix: +${indent(rendered.mkString("\n"), 1)}""" + } + + object SnakeCase { private val re = "[A-Z]+".r diff --git a/src/main/scala/sbtghactions/WorkflowJob.scala b/src/main/scala/sbtghactions/WorkflowJob.scala index f7bd3b3..6ac2e0d 100644 --- a/src/main/scala/sbtghactions/WorkflowJob.scala +++ b/src/main/scala/sbtghactions/WorkflowJob.scala @@ -32,7 +32,8 @@ final case class WorkflowJob( matrixExcs: List[MatrixExclude] = List(), runsOnExtraLabels: List[String] = List(), container: Option[JobContainer] = None, - environment: Option[JobEnvironment] = None) { + environment: Option[JobEnvironment] = None, + uses: Option[WorkflowRef] = None) { def needsJob(job: WorkflowJob): WorkflowJob = copy(needs = needs :+ job.id) diff --git a/src/main/scala/sbtghactions/WorkflowRef.scala b/src/main/scala/sbtghactions/WorkflowRef.scala new file mode 100644 index 0000000..ec6528e --- /dev/null +++ b/src/main/scala/sbtghactions/WorkflowRef.scala @@ -0,0 +1,30 @@ +/* + * Copyright 2020-2021 Daniel Spiewak + * + * 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 sbtghactions + +case class WorkflowRef( + workflowPath: String, + ref: String, + inputs: Map[String, String], + secrets: Map[String, String] +) { + lazy val render: String = + s"uses: $workflowPath@$ref$renderInputs$renderSecrets" + + private def renderInputs = RenderFunctions.renderMap(inputs, "with") + private def renderSecrets = RenderFunctions.renderMap(secrets, "secrets") +} diff --git a/src/test/scala/sbtghactions/GenerativePluginSpec.scala b/src/test/scala/sbtghactions/GenerativePluginSpec.scala index 478eebb..101eeb0 100644 --- a/src/test/scala/sbtghactions/GenerativePluginSpec.scala +++ b/src/test/scala/sbtghactions/GenerativePluginSpec.scala @@ -596,6 +596,37 @@ class GenerativePluginSpec extends Specification { uses: actions/checkout@v2""" } + "compile a simple job that references a reusable workflow" in { + val results = compileJob( + WorkflowJob( + "bippy", + "Bippity Bop Around the Clock", + Nil, + uses = Some( + WorkflowRef.apply( + "some/path/.github/workflow/file.yml", + "master", + Map("git-ref" -> "${{ github.head_ref }}"), + Map("MY_SECRET" -> "${{ secrets.SECRET }}") + ) + ) + ), + "") + + results mustEqual s"""bippy: + name: Bippity Bop Around the Clock + strategy: + matrix: + os: [ubuntu-latest] + scala: [2.13.6] + java: [temurin@11] + runs-on: $${{ matrix.os }} + uses: some/path/.github/workflow/file.yml@masterwith: + git-ref: $${{ github.head_ref }}secrets: + MY_SECRET: $${{ secrets.SECRET }}""" + } + + "compile a job with one step and three oses" in { val results = compileJob( WorkflowJob( From 9fa66df8ed578f7348a257bcfeaa9c6f66de84f1 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> Date: Wed, 12 Jan 2022 00:31:27 +0100 Subject: [PATCH 17/23] Check githubWorkflowIncludeClean to generate clean workflow as well --- src/main/scala/sbtghactions/GenerativePlugin.scala | 3 ++- src/main/scala/sbtghactions/RenderFunctions.scala | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index c1f1c4a..5486373 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -794,7 +794,8 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)} IO.write(ciYml, ciContents) } - if (targets(GenerationTarget.Clean)) { + // TODO think about migration + if (targets(GenerationTarget.Clean) || includeClean) { IO.write(cleanYml, cleanContents) } diff --git a/src/main/scala/sbtghactions/RenderFunctions.scala b/src/main/scala/sbtghactions/RenderFunctions.scala index c9bb893..7a0ab50 100644 --- a/src/main/scala/sbtghactions/RenderFunctions.scala +++ b/src/main/scala/sbtghactions/RenderFunctions.scala @@ -16,8 +16,6 @@ package sbtghactions -import sbtghactions.GenerativePlugin.{indent, isSafeString, wrap} - object RenderFunctions { def renderBranches(branches: Seq[String]): String = From fe9cacc2dd31e359043a725552eaa6d75074e8c8 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> Date: Wed, 12 Jan 2022 07:35:03 +0100 Subject: [PATCH 18/23] Fix reusable workflow rendering --- src/main/scala/sbtghactions/RenderFunctions.scala | 2 +- src/main/scala/sbtghactions/WorkflowRef.scala | 2 +- src/test/scala/sbtghactions/GenerativePluginSpec.scala | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/scala/sbtghactions/RenderFunctions.scala b/src/main/scala/sbtghactions/RenderFunctions.scala index 7a0ab50..4107126 100644 --- a/src/main/scala/sbtghactions/RenderFunctions.scala +++ b/src/main/scala/sbtghactions/RenderFunctions.scala @@ -82,7 +82,7 @@ object RenderFunctions { s"""$key: ${wrap(value)}""" } - s"""$prefix: + s"""\n$prefix: ${indent(rendered.mkString("\n"), 1)}""" } diff --git a/src/main/scala/sbtghactions/WorkflowRef.scala b/src/main/scala/sbtghactions/WorkflowRef.scala index ec6528e..58d8512 100644 --- a/src/main/scala/sbtghactions/WorkflowRef.scala +++ b/src/main/scala/sbtghactions/WorkflowRef.scala @@ -23,7 +23,7 @@ case class WorkflowRef( secrets: Map[String, String] ) { lazy val render: String = - s"uses: $workflowPath@$ref$renderInputs$renderSecrets" + s"""uses: "$workflowPath@$ref"$renderInputs$renderSecrets""" private def renderInputs = RenderFunctions.renderMap(inputs, "with") private def renderSecrets = RenderFunctions.renderMap(secrets, "secrets") diff --git a/src/test/scala/sbtghactions/GenerativePluginSpec.scala b/src/test/scala/sbtghactions/GenerativePluginSpec.scala index 101eeb0..1a16cc2 100644 --- a/src/test/scala/sbtghactions/GenerativePluginSpec.scala +++ b/src/test/scala/sbtghactions/GenerativePluginSpec.scala @@ -621,8 +621,10 @@ class GenerativePluginSpec extends Specification { scala: [2.13.6] java: [temurin@11] runs-on: $${{ matrix.os }} - uses: some/path/.github/workflow/file.yml@masterwith: - git-ref: $${{ github.head_ref }}secrets: + uses: "some/path/.github/workflow/file.yml@master" + with: + git-ref: $${{ github.head_ref }} + secrets: MY_SECRET: $${{ secrets.SECRET }}""" } From d09b0d7baed2c9151978efec88ce97fbb074c652 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> Date: Wed, 12 Jan 2022 08:07:46 +0100 Subject: [PATCH 19/23] Make reusable workflow jobs another class This ensures on type level that no invalid jobs can be created --- .../scala/sbtghactions/GenerativePlugin.scala | 26 +++++++++++-------- src/main/scala/sbtghactions/Workflow.scala | 2 +- src/main/scala/sbtghactions/WorkflowJob.scala | 13 ++++++++-- .../sbtghactions/GenerativePluginSpec.scala | 14 +++------- 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index 5486373..f6c57b0 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -273,11 +273,21 @@ ${indent(rendered.mkString("\n"), 1)}""" } - def compileJob(job: WorkflowJob, sbt: String): String = { - //TODO better job data structure to avoid runtime errors - if(job.steps.nonEmpty && job.uses.nonEmpty) - sys.error(s"A job can either uses a reusable workflow or define steps. Please edit ${job.id}") + def compileJob(job: WorkflowJobBase, sbt: String): String = + job match { + case ReusableWorkflowJob(id, name, uses, cond, needs) => + val renderedNeeds = if (needs.isEmpty) + "" + else + s"\nneeds: [${needs.mkString(", ")}]" + + val renderedCond = cond.map(wrap).map("\nif: " + _).getOrElse("") + val body = + s"""|name: ${wrap(name)}$renderedNeeds$renderedCond + |${uses.render}""".stripMargin + s"$id:\n${indent(body, 1)}" + case job: WorkflowJob => val renderedNeeds = if (job.needs.isEmpty) "" else @@ -440,15 +450,9 @@ ${indent(rendered.mkString("\n"), 1)}""" "" } - val renderedUses = - job.uses match { - case Some(value) => value.render - case None => "" - } - val body = s"""name: ${wrap(job.name)}${renderedNeeds}${renderedCond}${renderedStrategy} runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedEnv} -$renderedSteps$renderedUses""" +$renderedSteps""" s"${job.id}:\n${indent(body, 1)}" } diff --git a/src/main/scala/sbtghactions/Workflow.scala b/src/main/scala/sbtghactions/Workflow.scala index aa7c9ca..73f5c76 100644 --- a/src/main/scala/sbtghactions/Workflow.scala +++ b/src/main/scala/sbtghactions/Workflow.scala @@ -21,7 +21,7 @@ import sbtghactions.RenderFunctions.{indentOnce, wrap} final case class Workflow( name: String, ons: Seq[TriggerEvent], - jobs: Seq[WorkflowJob], + jobs: Seq[WorkflowJobBase], env: Map[String, String]) { def render: String = diff --git a/src/main/scala/sbtghactions/WorkflowJob.scala b/src/main/scala/sbtghactions/WorkflowJob.scala index 6ac2e0d..6522c95 100644 --- a/src/main/scala/sbtghactions/WorkflowJob.scala +++ b/src/main/scala/sbtghactions/WorkflowJob.scala @@ -16,6 +16,16 @@ package sbtghactions +sealed trait WorkflowJobBase + +final case class ReusableWorkflowJob( + id: String, + name: String, + uses: WorkflowRef, + cond: Option[String] = None, + needs: List[String] = List(), +) extends WorkflowJobBase + final case class WorkflowJob( id: String, name: String, @@ -32,8 +42,7 @@ final case class WorkflowJob( matrixExcs: List[MatrixExclude] = List(), runsOnExtraLabels: List[String] = List(), container: Option[JobContainer] = None, - environment: Option[JobEnvironment] = None, - uses: Option[WorkflowRef] = None) { + environment: Option[JobEnvironment] = None) extends WorkflowJobBase { def needsJob(job: WorkflowJob): WorkflowJob = copy(needs = needs :+ job.id) diff --git a/src/test/scala/sbtghactions/GenerativePluginSpec.scala b/src/test/scala/sbtghactions/GenerativePluginSpec.scala index 1a16cc2..e7e5f8d 100644 --- a/src/test/scala/sbtghactions/GenerativePluginSpec.scala +++ b/src/test/scala/sbtghactions/GenerativePluginSpec.scala @@ -598,29 +598,21 @@ class GenerativePluginSpec extends Specification { "compile a simple job that references a reusable workflow" in { val results = compileJob( - WorkflowJob( + ReusableWorkflowJob( "bippy", "Bippity Bop Around the Clock", - Nil, - uses = Some( - WorkflowRef.apply( + uses = + WorkflowRef( "some/path/.github/workflow/file.yml", "master", Map("git-ref" -> "${{ github.head_ref }}"), Map("MY_SECRET" -> "${{ secrets.SECRET }}") ) - ) ), "") results mustEqual s"""bippy: name: Bippity Bop Around the Clock - strategy: - matrix: - os: [ubuntu-latest] - scala: [2.13.6] - java: [temurin@11] - runs-on: $${{ matrix.os }} uses: "some/path/.github/workflow/file.yml@master" with: git-ref: $${{ github.head_ref }} From 7c90b65c76dd740d7dd1ad916a2d7feaea765299 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> Date: Fri, 3 Feb 2023 15:51:49 +0100 Subject: [PATCH 20/23] Integrate permissions into the workflow DSL --- .../scala/sbtghactions/GenerativePlugin.scala | 186 ++---------------- .../scala/sbtghactions/PermissionScope.scala | 31 ++- .../scala/sbtghactions/RenderFunctions.scala | 12 +- src/main/scala/sbtghactions/Workflow.scala | 18 +- .../scala/sbtghactions/WorkflowStep.scala | 7 + .../sbtghactions/GenerativePluginSpec.scala | 39 ++-- 6 files changed, 100 insertions(+), 193 deletions(-) diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index 24a72a7..b767a23 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -16,8 +16,9 @@ package sbtghactions -import sbt.Keys._ -import sbt._ +import sbt.* +import sbt.Keys.* +import sbtghactions.RenderFunctions.* import java.nio.file.FileSystems import scala.io.Source @@ -62,41 +63,13 @@ object GenerativePlugin extends AutoPlugin { val JavaSpec = sbtghactions.JavaSpec } - import autoImport._ + import autoImport.* private def indent(output: String, level: Int): String = { val space = (0 until level * 2).map(_ => ' ').mkString (space + output.replace("\n", s"\n$space")).replaceAll("""\n[ ]+\n""", "\n\n") } - private def isSafeString(str: String): Boolean = - !(str.indexOf(':') >= 0 || // pretend colon is illegal everywhere for simplicity - str.indexOf('#') >= 0 || // same for comment - str.indexOf('!') == 0 || - str.indexOf('*') == 0 || - str.indexOf('-') == 0 || - str.indexOf('?') == 0 || - str.indexOf('{') == 0 || - str.indexOf('}') == 0 || - str.indexOf('[') == 0 || - str.indexOf(']') == 0 || - str.indexOf(',') == 0 || - str.indexOf('|') == 0 || - str.indexOf('>') == 0 || - str.indexOf('@') == 0 || - str.indexOf('`') == 0 || - str.indexOf('"') == 0 || - str.indexOf('\'') == 0 || - str.indexOf('&') == 0) - - private def wrap(str: String): String = - if (str.indexOf('\n') >= 0) - "|\n" + indent(str, 1) - else if (isSafeString(str)) - str - else - s"'${str.replace("'", "''")}'" - def compileList(items: List[String], level: Int): String = { val rendered = items.map(wrap) if (rendered.map(_.length).sum < 40) // just arbitrarily... @@ -115,7 +88,7 @@ object GenerativePlugin extends AutoPlugin { } mkString "\n" def compilePREventType(tpe: PREventType): String = { - import PREventType._ + import PREventType.* tpe match { case Assigned => "assigned" @@ -171,77 +144,20 @@ object GenerativePlugin extends AutoPlugin { s"environment: ${wrap(environment.name)}" } - def compileEnv(env: Map[String, String], prefix: String = "env"): String = - if (env.isEmpty) { - "" - } else { - val rendered = env map { - case (key, value) => - if (!isSafeString(key) || key.indexOf(' ') >= 0) - sys.error(s"'$key' is not a valid environment variable name") - - s"""$key: ${wrap(value)}""" - } -s"""$prefix: -${indent(rendered.mkString("\n"), 1)}""" - } - - def compilePermissionScope(permissionScope: PermissionScope): String = permissionScope match { - case PermissionScope.Actions => "actions" - case PermissionScope.Checks => "checks" - case PermissionScope.Contents => "contents" - case PermissionScope.Deployments => "deployments" - case PermissionScope.IdToken => "id-token" - case PermissionScope.Issues => "issues" - case PermissionScope.Discussions => "discussions" - case PermissionScope.Packages => "packages" - case PermissionScope.Pages => "pages" - case PermissionScope.PullRequests => "pull-requests" - case PermissionScope.RepositoryProjects => "repository-projects" - case PermissionScope.SecurityEvents => "security-events" - case PermissionScope.Statuses => "statuses" - } - - def compilePermissionsValue(permissionValue: PermissionValue): String = permissionValue match { - case PermissionValue.Read => "read" - case PermissionValue.Write => "write" - case PermissionValue.None => "none" - } - - def compilePermissions(permissions: Option[Permissions]): String = { - permissions match { - case Some(perms) => - val rendered = perms match { - case Permissions.ReadAll => " read-all" - case Permissions.WriteAll => " write-all" - case Permissions.None => " {}" - case Permissions.Specify(permMap) => - val map = permMap.map{ - case (key, value) => - s"${compilePermissionScope(key)}: ${compilePermissionsValue(value)}" - } - "\n" + indent(map.mkString("\n"), 1) - } - s"permissions:$rendered" - - case None => "" - } - } def compileStep( step: WorkflowStep, sbt: String, sbtStepPreamble: List[String] = WorkflowStep.DefaultSbtStepPreamble, - declareShell: Boolean = false - , scalaMatrixBuild: Boolean = true): String = { - import WorkflowStep._ + declareShell: Boolean = false): String = { + import WorkflowStep.* val renderedName = step.name.map(wrap).map("name: " + _ + "\n").getOrElse("") val renderedId = step.id.map(wrap).map("id: " + _ + "\n").getOrElse("") val renderedCond = step.cond.map(wrap).map("if: " + _ + "\n").getOrElse("") val renderedShell = if (declareShell) "shell: bash\n" else "" - val renderedEnvPre = compileEnv(step.env) + val renderedEnvPre = step.renderEnv val renderedEnv = if (renderedEnvPre.isEmpty) "" else @@ -261,7 +177,6 @@ ${indent(rendered.mkString("\n"), 1)}""" case sbtStep: Sbt => import sbtStep.commands - val version = if (scalaMatrixBuild) "++${{ matrix.scala }}" else "" val sbtClientMode = sbt.matches("""sbt.* --client($| .*)""") val safeCommands = if (sbtClientMode) s"'${(sbtStepPreamble ::: commands).mkString("; ")}'" @@ -279,9 +194,7 @@ ${indent(rendered.mkString("\n"), 1)}""" ) case use: Use => - import use.{ref, params} - - val decl = ref match { + val decl = use.ref match { case UseRef.Public(owner, repo, ref) => s"uses: $owner/$repo@$ref" @@ -300,25 +213,14 @@ ${indent(rendered.mkString("\n"), 1)}""" s"uses: docker://$image:$tag" } - decl + renderParams(params) + decl + use.renderParams } - indent(preamble + body, 1).updated(0, '-') + indentOnce(preamble + body).updated(0, '-') } def renderRunBody(commands: List[String], params: Map[String, String], renderedShell: String) = - renderedShell + "run: " + wrap(commands.mkString("\n")) + renderParams(params) - - def renderParams(params: Map[String, String]): String = { - val renderedParamsPre = compileEnv(params, prefix = "with") - val renderedParams = if (renderedParamsPre.isEmpty) - "" - else - "\n" + renderedParamsPre - - renderedParams - } - + renderedShell + "run: " + wrap(commands.mkString("\n")) + renderMap(params, "with") def compileJob(job: WorkflowJobBase, sbt: String): String = job match { @@ -359,10 +261,7 @@ ${indent(rendered.mkString("\n"), 1)}""" "" } - val renderedEnv = if (!env.isEmpty) - "\n" + compileEnv(env) - else - "" + val renderedEnv = renderMap(env, "env") val renderedVolumes = if (!volumes.isEmpty) s"\nvolumes:${compileList(volumes.toList map { case (l, r) => s"$l:$r" }, 1)}" @@ -386,17 +285,8 @@ ${indent(rendered.mkString("\n"), 1)}""" "" } - val renderedEnvPre = compileEnv(job.env) - val renderedEnv = if (renderedEnvPre.isEmpty) - "" - else - "\n" + renderedEnvPre - - val renderedPermPre = compilePermissions(job.permissions) - val renderedPerm = if (renderedPermPre.isEmpty) - "" - else - "\n" + renderedPermPre + val renderedEnv = renderMap(job.env, "env") + val renderedPerm = job.permissions.map(_.render).mkString List("include", "exclude") foreach { key => if (job.matrixAdds.contains(key)) { @@ -470,18 +360,6 @@ ${indent(rendered.mkString("\n"), 1)}""" val renderedFailFast = job.matrixFailFast.fold("")("\n fail-fast: " + _) - //val body = s"""name: ${wrap(job.name)}${renderedNeeds}${renderedCond} - //strategy:${renderedFailFast} - // matrix: - // os:${compileList(job.oses, 3)} - // scala:${compileList(job.scalas, 3)} - // java:${compileList(job.javas.map(_.render), 3)}${renderedMatrices} - //runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedPerm}${renderedEnv} - //steps: - //${indent(job.steps.map(compileStep(_, sbt, job.sbtStepPreamble, declareShell = declareShell)).mkString("\n\n"), 1)}""" - // - // s"${job.id}:\n${indent(body, 1)}" - val renderedOses = if (job.oses.isEmpty) "" else s"\n os:${compileList(job.oses, 3)}" val renderedScalas = @@ -507,7 +385,7 @@ ${indent(rendered.mkString("\n"), 1)}""" s"""steps: |${indent( job.steps.map( - compileStep(_, sbt, declareShell = declareShell, scalaMatrixBuild = job.scalas.nonEmpty) + compileStep(_, sbt, job.sbtStepPreamble, declareShell) ).mkString("\n\n"), 1) }""".stripMargin @@ -516,36 +394,14 @@ ${indent(rendered.mkString("\n"), 1)}""" } val body = s"""name: ${wrap(job.name)}${renderedNeeds}${renderedCond}${renderedStrategy} -runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedEnv} +runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedEnv}${renderedPerm} $renderedSteps""" - s"${job.id}:\n${indent(body, 1)}" + s"${job.id}:\n${indentOnce(body)}" } -//compileWorkflow( -// "Continuous Integration", -// githubWorkflowTargetBranches.value.toList, -// githubWorkflowTargetTags.value.toList, -// githubWorkflowTargetPaths.value, -// githubWorkflowPREventTypes.value.toList, -// githubWorkflowPermissions.value, -// githubWorkflowEnv.value, -// githubWorkflowGeneratedCI.value.toList, -// sbt) def compileWorkflow(workflow: Workflow, sbt: String): String = { - val renderedPermissionsPre = compilePermissions(permissions) - val renderedEnvPre = compileEnv(workflow.env) - val renderedEnv = if (renderedEnvPre.isEmpty) - "" - else - renderedEnvPre + "\n\n" - val renderedPerm = if (renderedPermissionsPre.isEmpty) - "" - else - renderedPermissionsPre + "\n\n" - - s"""# This file was automatically generated by sbt-github-actions using the # githubWorkflowGenerate task. You should add and commit this file to # your git repository. It goes without saying that you shouldn't edit @@ -554,9 +410,8 @@ $renderedSteps""" # to meet your needs, then regenerate this file. ${workflow.render} - -${renderedPerm}${renderedEnv}jobs: -${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)} +jobs: +${indentOnce(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"))} """ } @@ -790,6 +645,7 @@ ${indent(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)} ), githubWorkflowGeneratedCI.value.toList, githubWorkflowEnv.value, + githubWorkflowPermissions.value, ), sbt) } diff --git a/src/main/scala/sbtghactions/PermissionScope.scala b/src/main/scala/sbtghactions/PermissionScope.scala index 214628f..94bfad4 100644 --- a/src/main/scala/sbtghactions/PermissionScope.scala +++ b/src/main/scala/sbtghactions/PermissionScope.scala @@ -16,7 +16,30 @@ package sbtghactions -sealed trait Permissions extends Product with Serializable +import sbtghactions.RenderFunctions.* + +sealed trait Permissions extends Product with Serializable { + def compilePermissionsValue(permissionValue: PermissionValue): String = permissionValue match { + case PermissionValue.Read => "read" + case PermissionValue.Write => "write" + case PermissionValue.None => "none" + } + + def render: String = { + val rendered = this match { + case Permissions.ReadAll => " read-all" + case Permissions.WriteAll => " write-all" + case Permissions.None => " {}" + case Permissions.Specify(permMap) => + val map = permMap.map { + case (key, value) => + s"${key.render}: ${compilePermissionsValue(value)}" + } + "\n" + indent(map.mkString("\n"), 1) + } + s"\npermissions:$rendered" + } +} /** * @see https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs#overview @@ -26,9 +49,13 @@ object Permissions { case object WriteAll extends Permissions case object None extends Permissions final case class Specify(values: Map[PermissionScope, PermissionValue]) extends Permissions + + def specify(values: (PermissionScope, PermissionValue)*): Specify = Specify(values.toMap) } -sealed trait PermissionScope extends Product with Serializable +sealed trait PermissionScope extends Product with Serializable{ + def render: String = KebabCase(productPrefix) +} object PermissionScope { case object Actions extends PermissionScope diff --git a/src/main/scala/sbtghactions/RenderFunctions.scala b/src/main/scala/sbtghactions/RenderFunctions.scala index 4107126..b69da89 100644 --- a/src/main/scala/sbtghactions/RenderFunctions.scala +++ b/src/main/scala/sbtghactions/RenderFunctions.scala @@ -82,8 +82,9 @@ object RenderFunctions { s"""$key: ${wrap(value)}""" } - s"""\n$prefix: -${indent(rendered.mkString("\n"), 1)}""" + s""" +$prefix: +${indentOnce(rendered.mkString("\n"))}""" } @@ -94,4 +95,11 @@ ${indent(rendered.mkString("\n"), 1)}""" re.replaceAllIn(property.head.toLower +: property.tail, { m => s"_${m.matched.toLowerCase}" }) } + object KebabCase { + private val re = "[A-Z]+".r + + def apply(property: String): String = + re.replaceAllIn(property.head.toLower +: property.tail, { m => s"-${m.matched.toLowerCase}" }) + } + } diff --git a/src/main/scala/sbtghactions/Workflow.scala b/src/main/scala/sbtghactions/Workflow.scala index 73f5c76..9294660 100644 --- a/src/main/scala/sbtghactions/Workflow.scala +++ b/src/main/scala/sbtghactions/Workflow.scala @@ -16,16 +16,28 @@ package sbtghactions -import sbtghactions.RenderFunctions.{indentOnce, wrap} +import sbtghactions.RenderFunctions.* final case class Workflow( name: String, ons: Seq[TriggerEvent], jobs: Seq[WorkflowJobBase], - env: Map[String, String]) { + env: Map[String, String], + permissions: Option[Permissions], +) { def render: String = s"""|name: ${wrap(name)} | - |on:\n${ons.map(_.render).map(indentOnce).mkString("\n")}""".stripMargin + |on:\n$renderOns$renderPermissions$renderEnv""".stripMargin + + private def renderOns = + ons.map(_.render).map(indentOnce).mkString("\n") + + private def renderPermissions = + permissions.map(_.render).mkString + + private def renderEnv: String = + renderMap(env, "env") + } diff --git a/src/main/scala/sbtghactions/WorkflowStep.scala b/src/main/scala/sbtghactions/WorkflowStep.scala index 30efd4a..c2a2fd8 100644 --- a/src/main/scala/sbtghactions/WorkflowStep.scala +++ b/src/main/scala/sbtghactions/WorkflowStep.scala @@ -23,6 +23,13 @@ sealed trait WorkflowStep extends Product with Serializable { def name: Option[String] def cond: Option[String] def env: Map[String, String] + def params: Map[String, String] + def renderEnv: String = + RenderFunctions.renderMap(env, "env").drop(1) + + def renderParams: String = + RenderFunctions.renderMap(params, "with") + } object WorkflowStep { diff --git a/src/test/scala/sbtghactions/GenerativePluginSpec.scala b/src/test/scala/sbtghactions/GenerativePluginSpec.scala index a0cf7b6..37b7d63 100644 --- a/src/test/scala/sbtghactions/GenerativePluginSpec.scala +++ b/src/test/scala/sbtghactions/GenerativePluginSpec.scala @@ -41,9 +41,8 @@ class GenerativePluginSpec extends Specification { | branches: [main] | push: | branches: [main] - | |jobs: - |${" " * 2} + | |""".stripMargin compileWorkflow( @@ -55,6 +54,7 @@ class GenerativePluginSpec extends Specification { ), Nil, Map(), + None, ), "sbt") mustEqual expected } @@ -69,9 +69,8 @@ class GenerativePluginSpec extends Specification { | push: | branches: [main] | tags: [howdy] - | |jobs: - |${" " * 2} + | |""".stripMargin compileWorkflow( @@ -83,6 +82,7 @@ class GenerativePluginSpec extends Specification { ), Nil, Map(), + None, ), "sbt") mustEqual expected } @@ -97,9 +97,8 @@ class GenerativePluginSpec extends Specification { | types: [ready_for_review, review_requested, opened] | push: | branches: [main] - | |jobs: - |${" " * 2} + | |""".stripMargin compileWorkflow( @@ -114,6 +113,7 @@ class GenerativePluginSpec extends Specification { WebhookEvent.Push(List("main"), Nil, Paths.None)), Nil, Map(), + None, ), "sbt") mustEqual expected } @@ -125,10 +125,8 @@ class GenerativePluginSpec extends Specification { |on: | push: | branches: [main] - | |env: | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - | |jobs: | build: | name: Build and Test @@ -154,6 +152,7 @@ class GenerativePluginSpec extends Specification { ), Map( "GITHUB_TOKEN" -> s"$${{ secrets.GITHUB_TOKEN }}"), + None, ), "sbt") mustEqual expected } @@ -167,13 +166,10 @@ class GenerativePluginSpec extends Specification { | branches: [main, backport/v*] | push: | branches: [main, backport/v*] - | |permissions: | id-token: write - | |env: | GITHUB_TOKEN: $${{ secrets.GITHUB_TOKEN }} - | |jobs: | build: | name: Build and Test @@ -201,6 +197,7 @@ class GenerativePluginSpec extends Specification { List(WorkflowStep.Run(List("echo Hello World"))))), Map( "GITHUB_TOKEN" -> s"$${{ secrets.GITHUB_TOKEN }}"), + Some(Permissions.specify(PermissionScope.IdToken -> PermissionValue.Write)), ), "sbt") mustEqual expected } @@ -214,7 +211,6 @@ class GenerativePluginSpec extends Specification { | branches: [main] | push: | branches: [main] - | |jobs: | build: | name: Build and Test @@ -258,6 +254,7 @@ class GenerativePluginSpec extends Specification { "If we just didn't", List(WorkflowStep.Run(List("whoami"))))), Map(), + None, ), "") mustEqual expected } @@ -271,7 +268,6 @@ class GenerativePluginSpec extends Specification { | branches: [main] | push: | branches: [main] - | |jobs: | build: | name: Build and Test @@ -301,6 +297,7 @@ class GenerativePluginSpec extends Specification { container = Some( JobContainer("not:real-thing")))), Map(), + None, ), "") mustEqual expected } @@ -314,7 +311,6 @@ class GenerativePluginSpec extends Specification { | branches: [main] | push: | branches: [main] - | |jobs: | build: | name: Build and Test @@ -360,6 +356,7 @@ class GenerativePluginSpec extends Specification { ports = List(80, 443), options = List("--cpus", "1"))))), Map(), + None, ), "") mustEqual expected } @@ -375,9 +372,8 @@ class GenerativePluginSpec extends Specification { | push: | branches: [main] | paths: ['**.scala', '**.sbt'] - | |jobs: - |${" " * 2} + | |""".stripMargin compileWorkflow( @@ -397,7 +393,8 @@ class GenerativePluginSpec extends Specification { ) ), Nil, - Map() + Map(), + None, ), "sbt") mustEqual expected } @@ -413,9 +410,8 @@ class GenerativePluginSpec extends Specification { | push: | branches: [main] | paths-ignore: [docs/**] - | |jobs: - |${" " * 2} + | |""".stripMargin compileWorkflow( Workflow( @@ -434,7 +430,8 @@ class GenerativePluginSpec extends Specification { ) ), Nil, - Map() + Map(), + None, ), "sbt") mustEqual expected } } @@ -1075,8 +1072,8 @@ class GenerativePluginSpec extends Specification { } "predicate compilation" >> { - import Ref._ import RefPredicate._ + import Ref._ "equals" >> { compileBranchPredicate("thingy", Equals(Branch("other"))) mustEqual "thingy == 'refs/heads/other'" From cf4f7aeb1984b22a1e8a9e998d50357e8ff7ce3b Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> Date: Fri, 3 Feb 2023 18:21:22 +0100 Subject: [PATCH 21/23] Regenerate workflows --- .github/workflows/ci.yml | 2 -- src/main/scala/sbtghactions/GenerativePlugin.scala | 10 +++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cfa6e8f..fdd015e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,10 +13,8 @@ on: push: branches: ['**'] tags: [v*] - env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - jobs: build: name: Build and Test diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index b767a23..ae68f79 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -631,16 +631,16 @@ ${indentOnce(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"))} Workflow( "Continuous Integration", List( - WebhookEvent.Push( - githubWorkflowTargetBranches.value.toList, - githubWorkflowTargetTags.value.toList, - githubWorkflowTargetPaths.value - ), WebhookEvent.PullRequest( githubWorkflowTargetBranches.value.toList, Nil, githubWorkflowTargetPaths.value, githubWorkflowPREventTypes.value.toList + ), + WebhookEvent.Push( + githubWorkflowTargetBranches.value.toList, + githubWorkflowTargetTags.value.toList, + githubWorkflowTargetPaths.value ) ), githubWorkflowGeneratedCI.value.toList, From 2a5dc1f1f563a624305b8f259e5957cc6e6d4477 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> Date: Fri, 3 Feb 2023 19:24:19 +0100 Subject: [PATCH 22/23] Adjust test expectations --- src/main/scala/sbtghactions/GenerativePlugin.scala | 11 ----------- .../sbtghactions/check-and-regenerate/expected-ci.yml | 2 -- .../sbtghactions/no-clean/.github/workflows/ci.yml | 2 -- .../non-existent-target/.github/workflows/ci.yml | 2 -- .../sbt-native-thin-client/.github/workflows/ci.yml | 2 -- .../suppressed-scala-version/expected-ci.yml | 2 -- 6 files changed, 21 deletions(-) diff --git a/src/main/scala/sbtghactions/GenerativePlugin.scala b/src/main/scala/sbtghactions/GenerativePlugin.scala index ae68f79..e01698f 100644 --- a/src/main/scala/sbtghactions/GenerativePlugin.scala +++ b/src/main/scala/sbtghactions/GenerativePlugin.scala @@ -617,16 +617,6 @@ ${indentOnce(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"))} } else { githubWorkflowSbtCommand.value } - //compileWorkflow( - // "Continuous Integration", - // githubWorkflowTargetBranches.value.toList, - // githubWorkflowTargetTags.value.toList, - // githubWorkflowTargetPaths.value, - // githubWorkflowPREventTypes.value.toList, - // githubWorkflowPermissions.value, - // githubWorkflowEnv.value, - // githubWorkflowGeneratedCI.value.toList, - // sbt) compileWorkflow( Workflow( "Continuous Integration", @@ -724,7 +714,6 @@ ${indentOnce(workflow.jobs.map(compileJob(_, sbt)).mkString("\n\n"))} IO.write(ciYml, ciContents) } - // TODO think about migration if (targets(GenerationTarget.Clean) || includeClean) { IO.write(cleanYml, cleanContents) } diff --git a/src/sbt-test/sbtghactions/check-and-regenerate/expected-ci.yml b/src/sbt-test/sbtghactions/check-and-regenerate/expected-ci.yml index 2fa5c9b..1ef38be 100644 --- a/src/sbt-test/sbtghactions/check-and-regenerate/expected-ci.yml +++ b/src/sbt-test/sbtghactions/check-and-regenerate/expected-ci.yml @@ -13,10 +13,8 @@ on: push: branches: ['**'] tags: [v*] - env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - jobs: build: name: Build and Test diff --git a/src/sbt-test/sbtghactions/no-clean/.github/workflows/ci.yml b/src/sbt-test/sbtghactions/no-clean/.github/workflows/ci.yml index 136ec90..5c4778a 100644 --- a/src/sbt-test/sbtghactions/no-clean/.github/workflows/ci.yml +++ b/src/sbt-test/sbtghactions/no-clean/.github/workflows/ci.yml @@ -12,10 +12,8 @@ on: branches: ['**'] push: branches: ['**'] - env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - jobs: build: name: Build and Test diff --git a/src/sbt-test/sbtghactions/non-existent-target/.github/workflows/ci.yml b/src/sbt-test/sbtghactions/non-existent-target/.github/workflows/ci.yml index 166c67e..7e603d0 100644 --- a/src/sbt-test/sbtghactions/non-existent-target/.github/workflows/ci.yml +++ b/src/sbt-test/sbtghactions/non-existent-target/.github/workflows/ci.yml @@ -12,10 +12,8 @@ on: branches: ['**'] push: branches: ['**'] - env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - jobs: build: name: Build and Test diff --git a/src/sbt-test/sbtghactions/sbt-native-thin-client/.github/workflows/ci.yml b/src/sbt-test/sbtghactions/sbt-native-thin-client/.github/workflows/ci.yml index ff39f1c..168fc43 100644 --- a/src/sbt-test/sbtghactions/sbt-native-thin-client/.github/workflows/ci.yml +++ b/src/sbt-test/sbtghactions/sbt-native-thin-client/.github/workflows/ci.yml @@ -12,10 +12,8 @@ on: branches: ['**'] push: branches: ['**'] - env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - jobs: build: name: Build and Test diff --git a/src/sbt-test/sbtghactions/suppressed-scala-version/expected-ci.yml b/src/sbt-test/sbtghactions/suppressed-scala-version/expected-ci.yml index 4ffa035..b62103a 100644 --- a/src/sbt-test/sbtghactions/suppressed-scala-version/expected-ci.yml +++ b/src/sbt-test/sbtghactions/suppressed-scala-version/expected-ci.yml @@ -13,10 +13,8 @@ on: push: branches: ['**'] tags: [v*] - env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - jobs: build: name: Build and Test From 55e748def1f97d91500fe662013ef99a21fc1847 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> Date: Sat, 25 Feb 2023 17:27:55 +0100 Subject: [PATCH 23/23] Add GitHub doc links to Workflow DSL --- src/main/scala/sbtghactions/CheckRunEventType.scala | 3 +++ src/main/scala/sbtghactions/CheckSuiteEventType.scala | 3 +++ src/main/scala/sbtghactions/GenerationTarget.scala | 1 + src/main/scala/sbtghactions/IssueCommentEventType.scala | 3 +++ src/main/scala/sbtghactions/IssuesEventType.scala | 4 ++++ src/main/scala/sbtghactions/LabelEventType.scala | 4 ++++ src/main/scala/sbtghactions/MilestoneEventType.scala | 4 ++++ src/main/scala/sbtghactions/PREventType.scala | 4 ++++ src/main/scala/sbtghactions/PRReviewCommentEventType.scala | 4 ++++ src/main/scala/sbtghactions/PRReviewEventType.scala | 4 ++++ src/main/scala/sbtghactions/PRTargetEventType.scala | 3 +++ src/main/scala/sbtghactions/ProjectCardEventType.scala | 4 ++++ src/main/scala/sbtghactions/ProjectColumnEventType.scala | 4 ++++ src/main/scala/sbtghactions/ProjectEventType.scala | 4 ++++ src/main/scala/sbtghactions/RegistryPackageEventType.scala | 3 +++ src/main/scala/sbtghactions/ReleaseEventType.scala | 3 +++ src/main/scala/sbtghactions/TriggerEvent.scala | 4 ++++ src/main/scala/sbtghactions/WatchEventType.scala | 4 ++++ src/main/scala/sbtghactions/WorkflowRunEventType.scala | 3 +++ 19 files changed, 66 insertions(+) diff --git a/src/main/scala/sbtghactions/CheckRunEventType.scala b/src/main/scala/sbtghactions/CheckRunEventType.scala index 43ce5ad..1ae5dcc 100644 --- a/src/main/scala/sbtghactions/CheckRunEventType.scala +++ b/src/main/scala/sbtghactions/CheckRunEventType.scala @@ -16,6 +16,9 @@ package sbtghactions +/** + * @see https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#check_run + */ sealed trait CheckRunEventType extends EventType object CheckRunEventType { diff --git a/src/main/scala/sbtghactions/CheckSuiteEventType.scala b/src/main/scala/sbtghactions/CheckSuiteEventType.scala index 9757d74..259d0c3 100644 --- a/src/main/scala/sbtghactions/CheckSuiteEventType.scala +++ b/src/main/scala/sbtghactions/CheckSuiteEventType.scala @@ -16,6 +16,9 @@ package sbtghactions +/** + * @see https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#check_run + */ sealed trait CheckSuiteEventType extends EventType object CheckSuiteEventType { diff --git a/src/main/scala/sbtghactions/GenerationTarget.scala b/src/main/scala/sbtghactions/GenerationTarget.scala index f51e4b0..5face1c 100644 --- a/src/main/scala/sbtghactions/GenerationTarget.scala +++ b/src/main/scala/sbtghactions/GenerationTarget.scala @@ -16,6 +16,7 @@ package sbtghactions + sealed trait GenerationTarget extends Product with Serializable object GenerationTarget { diff --git a/src/main/scala/sbtghactions/IssueCommentEventType.scala b/src/main/scala/sbtghactions/IssueCommentEventType.scala index 2d72e98..a004bd4 100644 --- a/src/main/scala/sbtghactions/IssueCommentEventType.scala +++ b/src/main/scala/sbtghactions/IssueCommentEventType.scala @@ -16,6 +16,9 @@ package sbtghactions +/** + * @see https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#issue_comment + */ sealed trait IssueCommentEventType extends EventType object IssueCommentEventType { diff --git a/src/main/scala/sbtghactions/IssuesEventType.scala b/src/main/scala/sbtghactions/IssuesEventType.scala index 5832e71..8bd7d69 100644 --- a/src/main/scala/sbtghactions/IssuesEventType.scala +++ b/src/main/scala/sbtghactions/IssuesEventType.scala @@ -16,6 +16,10 @@ package sbtghactions +/** + * @see https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#issues + */ + sealed trait IssuesEventType extends EventType object IssuesEventType { diff --git a/src/main/scala/sbtghactions/LabelEventType.scala b/src/main/scala/sbtghactions/LabelEventType.scala index df40560..8e710ac 100644 --- a/src/main/scala/sbtghactions/LabelEventType.scala +++ b/src/main/scala/sbtghactions/LabelEventType.scala @@ -16,6 +16,10 @@ package sbtghactions +/** + * @see https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#label + */ + sealed trait LabelEventType extends EventType object LabelEventType { diff --git a/src/main/scala/sbtghactions/MilestoneEventType.scala b/src/main/scala/sbtghactions/MilestoneEventType.scala index 923172b..711422b 100644 --- a/src/main/scala/sbtghactions/MilestoneEventType.scala +++ b/src/main/scala/sbtghactions/MilestoneEventType.scala @@ -16,6 +16,10 @@ package sbtghactions +/** + * @see https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#issue_comment + */ + sealed trait MilestoneEventType extends EventType object MilestoneEventType { diff --git a/src/main/scala/sbtghactions/PREventType.scala b/src/main/scala/sbtghactions/PREventType.scala index 91ee0f9..2f283f1 100644 --- a/src/main/scala/sbtghactions/PREventType.scala +++ b/src/main/scala/sbtghactions/PREventType.scala @@ -16,6 +16,10 @@ package sbtghactions +/** + * @see https://docs.github.com/webhooks-and-events/webhooks/webhook-events-and-payloads#pull_request + */ + sealed trait PREventType extends EventType object PREventType { diff --git a/src/main/scala/sbtghactions/PRReviewCommentEventType.scala b/src/main/scala/sbtghactions/PRReviewCommentEventType.scala index beb18e7..56bb326 100644 --- a/src/main/scala/sbtghactions/PRReviewCommentEventType.scala +++ b/src/main/scala/sbtghactions/PRReviewCommentEventType.scala @@ -16,6 +16,10 @@ package sbtghactions +/** + * @see https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#issue_comment + */ + sealed trait PRReviewCommentEventType extends EventType object PRReviewCommentEventType { diff --git a/src/main/scala/sbtghactions/PRReviewEventType.scala b/src/main/scala/sbtghactions/PRReviewEventType.scala index 568e796..bcf9e2d 100644 --- a/src/main/scala/sbtghactions/PRReviewEventType.scala +++ b/src/main/scala/sbtghactions/PRReviewEventType.scala @@ -16,6 +16,10 @@ package sbtghactions + +/** + * @see https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#pull_request_review + */ sealed trait PRReviewEventType extends EventType object PRReviewEventType { diff --git a/src/main/scala/sbtghactions/PRTargetEventType.scala b/src/main/scala/sbtghactions/PRTargetEventType.scala index caa149d..27bc30c 100644 --- a/src/main/scala/sbtghactions/PRTargetEventType.scala +++ b/src/main/scala/sbtghactions/PRTargetEventType.scala @@ -16,6 +16,9 @@ package sbtghactions +/** + * @see https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target + */ sealed trait PRTargetEventType extends EventType object PRTargetEventType { diff --git a/src/main/scala/sbtghactions/ProjectCardEventType.scala b/src/main/scala/sbtghactions/ProjectCardEventType.scala index 61da9e9..a877382 100644 --- a/src/main/scala/sbtghactions/ProjectCardEventType.scala +++ b/src/main/scala/sbtghactions/ProjectCardEventType.scala @@ -16,6 +16,10 @@ package sbtghactions +/** + * @see https://docs.github.com/webhooks-and-events/webhooks/webhook-events-and-payloads#project_card + */ + sealed trait ProjectCardEventType extends EventType object ProjectCardEventType { diff --git a/src/main/scala/sbtghactions/ProjectColumnEventType.scala b/src/main/scala/sbtghactions/ProjectColumnEventType.scala index 409b0b7..b06a311 100644 --- a/src/main/scala/sbtghactions/ProjectColumnEventType.scala +++ b/src/main/scala/sbtghactions/ProjectColumnEventType.scala @@ -16,6 +16,10 @@ package sbtghactions +/** + * @see https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#project_column + */ + sealed trait ProjectColumnEventType extends EventType object ProjectColumnEventType { diff --git a/src/main/scala/sbtghactions/ProjectEventType.scala b/src/main/scala/sbtghactions/ProjectEventType.scala index ca02251..9a0162b 100644 --- a/src/main/scala/sbtghactions/ProjectEventType.scala +++ b/src/main/scala/sbtghactions/ProjectEventType.scala @@ -16,6 +16,10 @@ package sbtghactions +/** + * @see https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#project + */ + sealed trait ProjectEventType extends EventType object ProjectEventType { diff --git a/src/main/scala/sbtghactions/RegistryPackageEventType.scala b/src/main/scala/sbtghactions/RegistryPackageEventType.scala index 6fdeff0..7c737d8 100644 --- a/src/main/scala/sbtghactions/RegistryPackageEventType.scala +++ b/src/main/scala/sbtghactions/RegistryPackageEventType.scala @@ -16,6 +16,9 @@ package sbtghactions +/** + * @see https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#check_run + */ sealed trait RegistryPackageEventType extends EventType object RegistryPackageEventType { diff --git a/src/main/scala/sbtghactions/ReleaseEventType.scala b/src/main/scala/sbtghactions/ReleaseEventType.scala index 5177977..29c76a4 100644 --- a/src/main/scala/sbtghactions/ReleaseEventType.scala +++ b/src/main/scala/sbtghactions/ReleaseEventType.scala @@ -16,6 +16,9 @@ package sbtghactions +/** + * @see https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#release + */ sealed trait ReleaseEventType extends EventType object ReleaseEventType { diff --git a/src/main/scala/sbtghactions/TriggerEvent.scala b/src/main/scala/sbtghactions/TriggerEvent.scala index 517fa95..6e4529b 100644 --- a/src/main/scala/sbtghactions/TriggerEvent.scala +++ b/src/main/scala/sbtghactions/TriggerEvent.scala @@ -18,6 +18,10 @@ package sbtghactions import sbtghactions.RenderFunctions._ + +/** + * @see https://docs.github.com/en/actions/reference/events-that-trigger-workflows + */ sealed trait TriggerEvent extends Product with Serializable { val name: String = SnakeCase(productPrefix) def render: String diff --git a/src/main/scala/sbtghactions/WatchEventType.scala b/src/main/scala/sbtghactions/WatchEventType.scala index 6fbb8e8..3f6e04a 100644 --- a/src/main/scala/sbtghactions/WatchEventType.scala +++ b/src/main/scala/sbtghactions/WatchEventType.scala @@ -16,6 +16,10 @@ package sbtghactions +/** + * @see https://docs.github.com/en/actions/reference/events-that-trigger-workflows#watch + */ + sealed trait WatchEventType extends EventType object WatchEventType { diff --git a/src/main/scala/sbtghactions/WorkflowRunEventType.scala b/src/main/scala/sbtghactions/WorkflowRunEventType.scala index c4e4ee1..2ec294a 100644 --- a/src/main/scala/sbtghactions/WorkflowRunEventType.scala +++ b/src/main/scala/sbtghactions/WorkflowRunEventType.scala @@ -16,6 +16,9 @@ package sbtghactions +/** + * @see https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/webhook-events-and-payloads#workflow_run + */ sealed trait WorkflowRunEventType extends EventType object WorkflowRunEventType {