Skip to content

Commit c5eec92

Browse files
Vampirekrzema12
andauthored
Only validate changed typings in PR runs (#71)
Fixes #68 --------- Co-authored-by: Piotr Krzeminski <[email protected]>
1 parent db76dd0 commit c5eec92

File tree

2 files changed

+98
-25
lines changed

2 files changed

+98
-25
lines changed

.github/workflows/test.main.kts

Lines changed: 96 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
@file:DependsOn("io.github.typesafegithub:github-workflows-kt:3.3.0")
44
@file:DependsOn("it.krzeminski:snakeyaml-engine-kmp:3.1.1")
55
@file:DependsOn("io.github.optimumcode:json-schema-validator-jvm:0.5.1")
6+
@file:DependsOn("com.github.sya-ri:kgit:1.1.0")
67

78
@file:Repository("https://bindings.krzeminski.it")
89
@file:DependsOn("actions:checkout:v4")
910
@file:OptIn(ExperimentalKotlinLogicStep::class)
1011
@file:Suppress("UNCHECKED_CAST")
1112

13+
import com.github.syari.kgit.KGit
1214
import io.github.optimumcode.json.schema.ErrorCollector
1315
import io.github.optimumcode.json.schema.JsonSchema
1416
import io.github.optimumcode.json.schema.ValidationError
@@ -19,19 +21,29 @@ import io.github.typesafegithub.workflows.domain.triggers.Cron
1921
import io.github.typesafegithub.workflows.domain.triggers.PullRequest
2022
import io.github.typesafegithub.workflows.domain.triggers.Push
2123
import io.github.typesafegithub.workflows.domain.triggers.Schedule
24+
import io.github.typesafegithub.workflows.dsl.expressions.expr
2225
import io.github.typesafegithub.workflows.dsl.workflow
2326
import it.krzeminski.snakeyaml.engine.kmp.api.Load
2427
import kotlinx.serialization.json.JsonArray
2528
import kotlinx.serialization.json.JsonElement
2629
import kotlinx.serialization.json.JsonNull
2730
import kotlinx.serialization.json.JsonObject
2831
import kotlinx.serialization.json.JsonPrimitive
32+
import org.eclipse.jgit.diff.DiffEntry.ChangeType.DELETE
33+
import org.eclipse.jgit.treewalk.CanonicalTreeParser
34+
import org.eclipse.jgit.treewalk.filter.AndTreeFilter
35+
import org.eclipse.jgit.treewalk.filter.OrTreeFilter
36+
import org.eclipse.jgit.treewalk.filter.PathFilter
37+
import org.eclipse.jgit.treewalk.filter.PathSuffixFilter
2938
import java.io.File
3039
import java.io.IOException
3140
import java.net.URI
3241
import java.nio.file.Files
42+
import java.nio.file.Path
3343
import java.util.Collections.emptySet
44+
import java.util.stream.Stream
3445
import kotlin.io.path.Path
46+
import kotlin.io.path.extension
3547
import kotlin.io.path.invariantSeparatorsPathString
3648
import kotlin.io.path.name
3749

@@ -67,8 +79,13 @@ workflow(
6779
runsOn = UbuntuLatest,
6880
) {
6981
uses(action = Checkout())
70-
run(name = "Check for all actions") {
71-
validateTypings()
82+
run(
83+
name = "Check for actions",
84+
// TODO: replace this workaround once base_ref can be accessed natively:
85+
// https://github.com/typesafegithub/github-workflows-kt/issues/1946
86+
env = mapOf("base_ref" to expr { github.base_ref })
87+
) {
88+
validateTypings(github.sha, System.getenv("base_ref").ifEmpty { null })
7289
}
7390
}
7491

@@ -106,7 +123,7 @@ private data class ActionCoords(
106123
val pathToTypings: String,
107124
)
108125

109-
private fun validateTypings() {
126+
private fun validateTypings(sha: String, baseRef: String?) {
110127
val typingsSchema = JsonSchema.fromDefinition(
111128
URI.create("https://raw.githubusercontent.com/typesafegithub/github-actions-typing/" +
112129
"refs/heads/schema-latest/github-actions-typing.schema.json"
@@ -117,27 +134,8 @@ private fun validateTypings() {
117134
{ it.owner == "DamianReeves" && it.name == "write-file-action" },
118135
)
119136

120-
121-
val actionsWithYamlExtension = Files.walk(Path("typings"))
122-
.filter { it.name == "action-types.yaml" }
123-
.toList()
124-
check(actionsWithYamlExtension.isEmpty()) {
125-
"Some files have .yaml extension, and we'd like to use only .yml here: $actionsWithYamlExtension"
126-
}
127-
128-
val actions = Files.walk(Path("typings"))
129-
.filter { it.name == "action-types.yml" }
130-
.map {
131-
val (_, owner, name, version, pathAndYaml) = it.invariantSeparatorsPathString.split("/", limit = 5)
132-
val path = if ("/" in pathAndYaml) pathAndYaml.substringBeforeLast("/") else null
133-
ActionCoords(
134-
owner = owner,
135-
name = name,
136-
version = version,
137-
path = path,
138-
pathToTypings = it.invariantSeparatorsPathString,
139-
)
140-
}
137+
println()
138+
val actions = listActionsToValidate(sha = sha, baseRef = baseRef)
141139

142140
var shouldFail = false
143141

@@ -199,6 +197,80 @@ private fun validateTypings() {
199197
}
200198
}
201199

200+
private fun listActionsToValidate(sha: String, baseRef: String?): Stream<ActionCoords> =
201+
baseRef.let { baseRef ->
202+
if (baseRef == null) {
203+
println("Validating all typings")
204+
listAllActionManifestFilesInRepo()
205+
} else {
206+
println("Only validating changed typings")
207+
listAffectedActionManifestFiles(sha = sha, baseRef = baseRef)
208+
}.map {
209+
val (_, owner, name, version, pathAndYaml) = it.invariantSeparatorsPathString.split("/", limit = 5)
210+
val path = if ("/" in pathAndYaml) pathAndYaml.substringBeforeLast("/") else null
211+
ActionCoords(
212+
owner = owner,
213+
name = name,
214+
version = version,
215+
path = path,
216+
pathToTypings = it.invariantSeparatorsPathString,
217+
)
218+
}
219+
}
220+
221+
private fun listAllActionManifestFilesInRepo(): Stream<Path> {
222+
val actionsWithYamlExtension = Files.walk(Path("typings"))
223+
.filter { it.name == "action-types.yaml" }
224+
.toList()
225+
check(actionsWithYamlExtension.isEmpty()) {
226+
"Some files have .yaml extension, and we'd like to use only .yml here: $actionsWithYamlExtension"
227+
}
228+
229+
return Files.walk(Path("typings")).filter { it.name == "action-types.yml" }
230+
}
231+
232+
private fun listAffectedActionManifestFiles(sha: String, baseRef: String?): Stream<Path> {
233+
val typings = try {
234+
KGit.open(File(".")).use { git ->
235+
git.fetch {
236+
setRefSpecs("refs/heads/$baseRef:refs/heads/$baseRef")
237+
setDepth(1)
238+
}
239+
git.diff {
240+
setShowNameAndStatusOnly(true)
241+
git.repository.newObjectReader().use { objectReader ->
242+
setOldTree(CanonicalTreeParser().apply {
243+
reset(objectReader, git.repository.resolve("refs/heads/$baseRef^{tree}"))
244+
})
245+
setNewTree(CanonicalTreeParser().apply {
246+
reset(objectReader, git.repository.resolve("$sha^{tree}"))
247+
})
248+
}
249+
setPathFilter(
250+
AndTreeFilter.create(
251+
PathFilter.create("typings/"),
252+
OrTreeFilter.create(
253+
PathSuffixFilter.create("/action-types.yml"),
254+
PathSuffixFilter.create("/action-types.yaml"),
255+
),
256+
)
257+
)
258+
}
259+
.filter { it.changeType != DELETE }
260+
.map { Path(it.newPath) }
261+
.groupBy { it.extension == "yml" }
262+
}
263+
} finally {
264+
KGit.shutdown()
265+
}
266+
267+
check(typings[false].isNullOrEmpty()) {
268+
"Some files have .yaml extension, and we'd like to use only .yml here: ${typings[false]}"
269+
}
270+
271+
return typings[true]?.stream() ?: Stream.of()
272+
}
273+
202274
private fun loadTypings(path: String): Map<String, Any> =
203275
Load().loadOne(File(path).readText()) as Map<String, Any>
204276

.github/workflows/test.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ jobs:
4848
- id: 'step-0'
4949
uses: 'actions/checkout@v4'
5050
- id: 'step-1'
51-
name: 'Check for all actions'
51+
name: 'Check for actions'
5252
env:
53+
base_ref: '${{ github.base_ref }}'
5354
GHWKT_GITHUB_CONTEXT_JSON: '${{ toJSON(github) }}'
5455
run: 'GHWKT_RUN_STEP=''test.yaml:validate_typings:step-1'' ''.github/workflows/test.main.kts'''
5556
workflows_consistency_check:

0 commit comments

Comments
 (0)