diff --git a/action-binding-generator/api/action-binding-generator.api b/action-binding-generator/api/action-binding-generator.api index 3140406f28..9a9cd5a207 100644 --- a/action-binding-generator/api/action-binding-generator.api +++ b/action-binding-generator/api/action-binding-generator.api @@ -2,15 +2,18 @@ public abstract interface annotation class io/github/typesafegithub/workflows/ac } public final class io/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords; - public static synthetic fun copy$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords; + public final fun component4 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords; + public static synthetic fun copy$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords; public fun equals (Ljava/lang/Object;)Z public final fun getName ()Ljava/lang/String; public final fun getOwner ()Ljava/lang/String; + public final fun getTypesUuid ()Ljava/lang/String; public final fun getVersion ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -46,6 +49,7 @@ public final class io/github/typesafegithub/workflows/actionbindinggenerator/dom public final class io/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource : java/lang/Enum { public static final field ACTION Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource; + public static final field CUSTOM Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource; public static final field TYPING_CATALOG Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource; public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource; @@ -72,8 +76,8 @@ public final class io/github/typesafegithub/workflows/actionbindinggenerator/gen } public final class io/github/typesafegithub/workflows/actionbindinggenerator/generation/GenerationKt { - public static final fun generateBinding (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;)Ljava/util/List; - public static synthetic fun generateBinding$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;ILjava/lang/Object;)Ljava/util/List; + public static final fun generateBinding (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;Ljava/lang/String;)Ljava/util/List; + public static synthetic fun generateBinding$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;Ljava/lang/String;ILjava/lang/Object;)Ljava/util/List; } public final class io/github/typesafegithub/workflows/actionbindinggenerator/metadata/Input { diff --git a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords.kt b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords.kt index 6650508de2..30f4ad88c6 100644 --- a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords.kt +++ b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords.kt @@ -4,6 +4,7 @@ public data class ActionCoords( val owner: String, val name: String, val version: String, + val typesUuid: String? = null, ) /** @@ -12,7 +13,7 @@ public data class ActionCoords( */ public val ActionCoords.isTopLevel: Boolean get() = "/" !in name -public val ActionCoords.prettyPrint: String get() = "$owner/$name@$version" +public val ActionCoords.prettyPrint: String get() = "$owner/$name@$version${typesUuid?.let { " (types: $it)" } ?: ""}" /** * For most actions, it's the same as [ActionCoords.name]. diff --git a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource.kt b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource.kt index 094f3251b4..4a6fca4f9e 100644 --- a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource.kt +++ b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource.kt @@ -3,4 +3,5 @@ package io.github.typesafegithub.workflows.actionbindinggenerator.domain public enum class TypingActualSource { ACTION, TYPING_CATALOG, + CUSTOM, } diff --git a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt index 7ed27f4f41..dde767a041 100644 --- a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt +++ b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt @@ -57,24 +57,26 @@ public fun ActionCoords.generateBinding( metadataRevision: MetadataRevision, metadata: Metadata? = null, inputTypings: Pair, TypingActualSource?>? = null, + types: String? = null, ): List { val metadataResolved = metadata ?: this.fetchMetadata(metadataRevision) ?: return emptyList() val metadataProcessed = metadataResolved.removeDeprecatedInputsIfNameClash() - val inputTypingsResolved = inputTypings ?: this.provideTypes(metadataRevision) + val (inputTypingsResolved, typingActualSource) = + inputTypings ?: this.provideTypes(metadataRevision, types = types) val classNameUntyped = this.buildActionClassName() + "_Untyped" val actionBindingSourceCodeUntyped = generateActionBindingSourceCode(metadataProcessed, this, emptyMap(), classNameUntyped, untyped = true) val classNameAndSourceCodeTyped = - if (inputTypingsResolved.second != null) { + if (typingActualSource != null) { val className = this.buildActionClassName() val actionBindingSourceCode = generateActionBindingSourceCode( metadataProcessed, this, - inputTypingsResolved.first, + inputTypingsResolved, className, untyped = false, ) @@ -99,7 +101,7 @@ public fun ActionCoords.generateBinding( filePath = "io/github/typesafegithub/workflows/actions/$packageName/$className.kt", className = className, packageName = packageName, - typingActualSource = inputTypingsResolved.second, + typingActualSource = typingActualSource, ) }, ) diff --git a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProviding.kt b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProviding.kt index 56aeb5b3e2..68d62864aa 100644 --- a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProviding.kt +++ b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProviding.kt @@ -7,6 +7,7 @@ import io.github.typesafegithub.workflows.actionbindinggenerator.domain.Metadata import io.github.typesafegithub.workflows.actionbindinggenerator.domain.NewestForVersion import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource.ACTION +import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource.CUSTOM import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource.TYPING_CATALOG import io.github.typesafegithub.workflows.actionbindinggenerator.domain.repoName import io.github.typesafegithub.workflows.actionbindinggenerator.domain.subName @@ -21,10 +22,12 @@ import java.net.URI internal fun ActionCoords.provideTypes( metadataRevision: MetadataRevision, fetchUri: (URI) -> String = ::fetchUri, + types: String? = null, ): Pair, TypingActualSource?> = ( - this.fetchTypingMetadata(metadataRevision, fetchUri) - ?: this.toMajorVersion().fetchFromTypingsFromCatalog(fetchUri) + customTypingMetadata(types) + ?: this.fetchTypingMetadata(metadataRevision, fetchUri) + ?: this.toMajorVersion().fetchTypingsFromCatalog(fetchUri) )?.let { Pair(it.first.toTypesMap(), it.second) } ?: Pair(emptyMap(), null) @@ -42,6 +45,9 @@ private fun ActionCoords.catalogMetadata() = private fun ActionCoords.actionTypesYamlUrl(gitRef: String) = "https://raw.githubusercontent.com/$owner/$repoName/$gitRef$subName/action-types.yaml" +private fun customTypingMetadata(types: String? = null) = + types?.let { Pair(myYaml.decodeFromStringOrDefaultIfEmpty(it, ActionTypes()), CUSTOM) } + private fun ActionCoords.fetchTypingMetadata( metadataRevision: MetadataRevision, fetchUri: (URI) -> String = ::fetchUri, @@ -65,7 +71,7 @@ private fun ActionCoords.fetchTypingMetadata( return Pair(myYaml.decodeFromStringOrDefaultIfEmpty(typesMetadataYaml, ActionTypes()), ACTION) } -private fun ActionCoords.fetchFromTypingsFromCatalog(fetchUri: (URI) -> String = ::fetchUri): Pair? = +private fun ActionCoords.fetchTypingsFromCatalog(fetchUri: (URI) -> String = ::fetchUri): Pair? = ( fetchTypingsFromUrl(url = actionTypesFromCatalog(), fetchUri = fetchUri) ?: fetchTypingsForOlderVersionFromCatalog(fetchUri = fetchUri) diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProvidingTest.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProvidingTest.kt index c75621f270..e4cc2cde1d 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProvidingTest.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProvidingTest.kt @@ -85,6 +85,12 @@ class TypesProvidingTest : stored-in-typing-catalog: type: string """.trimIndent() + val custom = + """ + inputs: + custom: + type: string + """.trimIndent() val metadata = """ "versionsWithTypings": @@ -198,6 +204,70 @@ class TypesProvidingTest : types shouldBe Pair(mapOf("hosted-by-action-yml" to StringTyping), TypingActualSource.ACTION) } + test("only custom") { + // Given + val fetchUri: (URI) -> String = { throw IOException() } + val actionCoord = ActionCoords("some-owner", "some-name", "v3") + + // When + val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = custom) + + // Then + types shouldBe Pair(mapOf("custom" to StringTyping), TypingActualSource.CUSTOM) + } + + test("hosted by action, stored in typing catalog, and custom") { + // Given + val fetchUri: (URI) -> String = { + when (it) { + URI( + "https://raw.githubusercontent.com/some-owner/some-name/" + + "some-hash/action-types.yml", + ), + -> hostedByActionYml + URI( + "https://raw.githubusercontent.com/typesafegithub/github-actions-typing-catalog/" + + "main/typings/some-owner/some-name/v3/action-types.yml", + ), + -> storedInTypingCatalog + else -> throw IOException() + } + } + val actionCoord = ActionCoords("some-owner", "some-name", "v3") + + // When + val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = custom) + + // Then + types shouldBe Pair(mapOf("custom" to StringTyping), TypingActualSource.CUSTOM) + } + + test("hosted by action, stored in typing catalog, and empty custom") { + // Given + val fetchUri: (URI) -> String = { + when (it) { + URI( + "https://raw.githubusercontent.com/some-owner/some-name/" + + "some-hash/action-types.yml", + ), + -> hostedByActionYml + URI( + "https://raw.githubusercontent.com/typesafegithub/github-actions-typing-catalog/" + + "main/typings/some-owner/some-name/v3/action-types.yml", + ), + -> storedInTypingCatalog + else -> throw IOException() + } + } + val actionCoord = ActionCoords("some-owner", "some-name", "v3") + + // When + val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = "") + + // Then + types shouldBe Pair(emptyMap(), TypingActualSource.CUSTOM) + } + test("only stored in typing catalog for older version") { // Given val fetchUri: (URI) -> String = { diff --git a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/Main.kt b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/Main.kt index bae2eb452d..8c23c70cde 100644 --- a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/Main.kt +++ b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/Main.kt @@ -16,14 +16,17 @@ import io.ktor.server.application.call import io.ktor.server.application.install import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty +import io.ktor.server.request.receiveText import io.ktor.server.response.respondBytes import io.ktor.server.response.respondText import io.ktor.server.routing.Route import io.ktor.server.routing.get import io.ktor.server.routing.head +import io.ktor.server.routing.post import io.ktor.server.routing.route import io.ktor.server.routing.routing import io.opentelemetry.instrumentation.ktor.v2_0.server.KtorServerTracing +import java.util.UUID.randomUUID import kotlin.time.Duration.Companion.hours fun main() { @@ -66,7 +69,7 @@ fun main() { } get("/status") { - call.respondText("OK") + call.respondText(text = "OK") } } }.start(wait = true) @@ -86,7 +89,7 @@ private fun Route.metadata() { val bindingArtifacts = actionCoords.buildPackageArtifacts(githubToken = getGithubToken()) if (file in bindingArtifacts) { when (val artifact = bindingArtifacts[file]) { - is String -> call.respondText(artifact) + is String -> call.respondText(text = artifact) else -> call.respondText(text = "Not found", status = HttpStatusCode.NotFound) } } else { @@ -102,14 +105,14 @@ private fun Route.artifact( get { val bindingArtifacts = call.toBindingArtifacts(bindingsCache, refresh) if (bindingArtifacts == null) { - call.respondText("Not found", status = HttpStatusCode.NotFound) + call.respondText(text = "Not found", status = HttpStatusCode.NotFound) return@get } val file = call.parameters["file"]!! if (file in bindingArtifacts) { when (val artifact = bindingArtifacts[file]) { - is TextArtifact -> call.respondText(artifact.data) + is TextArtifact -> call.respondText(text = artifact.data) is JarArtifact -> call.respondBytes( bytes = artifact.data, @@ -127,39 +130,58 @@ private fun Route.artifact( val bindingArtifacts = call.toBindingArtifacts(bindingsCache, refresh) val file = call.parameters["file"]!! if (bindingArtifacts == null) { - call.respondText("Not found", status = HttpStatusCode.NotFound) + call.respondText(text = "Not found", status = HttpStatusCode.NotFound) return@head } if (file in bindingArtifacts) { - call.respondText("Exists", status = HttpStatusCode.OK) + call.respondText(text = "Exists", status = HttpStatusCode.OK) } else { call.respondText(text = "Not found", status = HttpStatusCode.NotFound) } } + + post { + val owner = "${call.parameters["owner"]}__types__${randomUUID()}" + val name = call.parameters["name"]!! + val version = call.parameters["version"]!! + val types = call.receiveText() + call.toBindingArtifacts(bindingsCache, refresh = true, owner = owner, types = types) + call.respondText(text = "$owner:$name:$version") + } } private suspend fun ApplicationCall.toBindingArtifacts( bindingsCache: Cache>>, refresh: Boolean, + owner: String = parameters["owner"]!!, + types: String? = null, ): Map? { - val owner = parameters["owner"]!! val name = parameters["name"]!! val version = parameters["version"]!! + val (ownerPlain, typesUuid) = + if (owner.contains("__types__")) { + owner + .split("__types__", limit = 2) + .let { it.first() to it[1] } + } else { + owner to null + } val actionCoords = ActionCoords( - owner = owner, + owner = ownerPlain, name = name, version = version, + typesUuid = typesUuid, ) println("➡️ Requesting ${actionCoords.prettyPrint}") val bindingArtifacts = if (refresh) { - actionCoords.buildVersionArtifacts().also { + actionCoords.buildVersionArtifacts(types ?: typesUuid?.let { "" }).also { bindingsCache.put(actionCoords, Result.of(it)) } } else { bindingsCache - .get(actionCoords) { Result.of(actionCoords.buildVersionArtifacts()) } + .get(actionCoords) { Result.of(actionCoords.buildVersionArtifacts(types ?: typesUuid?.let { "" })) } .getOrNull() } return bindingArtifacts diff --git a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/JarBuilding.kt b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/JarBuilding.kt index 78c1619546..5ad7082666 100644 --- a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/JarBuilding.kt +++ b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/JarBuilding.kt @@ -30,9 +30,10 @@ internal fun buildJars( owner: String, name: String, version: String, + types: String?, ): Jars? { val binding = - generateBinding(owner = owner, name = name, version = version).also { + generateBinding(owner = owner, name = name, version = version, types = types).also { if (it.isEmpty()) return null } val (sourceFilePaths, compilationInputDir) = binding.prepareDirectoryWithSources() @@ -54,6 +55,7 @@ private fun generateBinding( owner: String, name: String, version: String, + types: String?, ): List { val actionCoords = ActionCoords( @@ -63,6 +65,7 @@ private fun generateBinding( ) return actionCoords.generateBinding( metadataRevision = NewestForVersion, + types = types, ) } diff --git a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/VersionArtifactsBuilding.kt b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/VersionArtifactsBuilding.kt index eb9800c4a4..8a1f5f247e 100644 --- a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/VersionArtifactsBuilding.kt +++ b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/VersionArtifactsBuilding.kt @@ -13,8 +13,8 @@ data class JarArtifact( val data: ByteArray, ) : Artifact -fun ActionCoords.buildVersionArtifacts(): Map? { - val jars = buildJars(owner = owner, name = name.replace("__", "/"), version = version) ?: return null +fun ActionCoords.buildVersionArtifacts(types: String? = null): Map? { + val jars = buildJars(owner = owner, name = name.replace("__", "/"), version = version, types = types) ?: return null val pom = buildPomFile(owner = owner, name = name.replace("__", "/"), version = version) val module = buildModuleFile(owner = owner, name = name.replace("__", "/"), version = version) return mapOf(