From 517807bb213f9b65944859a9ec8e391ff9290e88 Mon Sep 17 00:00:00 2001 From: Piotr Krzeminski Date: Thu, 9 Oct 2025 23:18:21 +0200 Subject: [PATCH 1/9] WIP: provide bindings with code --- .../domain/ActionCoords.kt | 1 + .../ActionWithComment.kt | 80 +++++++++++++++++++ .../generation/GenerationTest.kt | 30 +++++++ .../workflows/domain/actions/Action.kt | 2 + .../jitbindingserver/ActionCoords.kt | 23 +++++- 5 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithComment.kt 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 682a68c510..e5a30f5307 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 @@ -15,6 +15,7 @@ public data class ActionCoords( */ val significantVersion: SignificantVersion = FULL, val path: String? = null, + val comment: String? = null, ) /** diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithComment.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithComment.kt new file mode 100644 index 0000000000..33238a4081 --- /dev/null +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithComment.kt @@ -0,0 +1,80 @@ +// This file was generated using action-binding-generator. Don't change it by hand, otherwise your +// changes will be overwritten with the next binding code regeneration. +// See https://github.com/typesafegithub/github-workflows-kt for more info. +@file:Suppress( + "DataClassPrivateConstructor", + "UNUSED_PARAMETER", +) + +package io.github.typesafegithub.workflows.actions.johnsmith + +import io.github.typesafegithub.workflows.domain.actions.Action +import io.github.typesafegithub.workflows.domain.actions.RegularAction +import java.util.LinkedHashMap +import kotlin.ExposedCopyVisibility +import kotlin.String +import kotlin.Suppress +import kotlin.Unit +import kotlin.collections.Map +import kotlin.collections.toList +import kotlin.collections.toTypedArray + +/** + * Action: Action with comment + * + * Do something cool + * + * [Action on GitHub](https://github.com/john-smith/action-with-comment) + * + * @param foo <required> Short description + * @param foo_Untyped <required> Short description + * @param _customInputs Type-unsafe map where you can put any inputs that are not yet supported by the binding + * @param _customVersion Allows overriding action's version, for example to use a specific minor version, or a newer version that the binding doesn't yet know about + */ +@ExposedCopyVisibility +public data class ActionWithComment private constructor( + /** + * <required> Short description + */ + public val foo: String? = null, + /** + * <required> Short description + */ + public val foo_Untyped: String? = null, + /** + * Type-unsafe map where you can put any inputs that are not yet supported by the binding + */ + public val _customInputs: Map = mapOf(), + /** + * Allows overriding action's version, for example to use a specific minor version, or a newer version that the binding doesn't yet know about + */ + public val _customVersion: String? = null, +) : RegularAction("john-smith", "action-with-comment", _customVersion ?: "v3") { + init { + require(!((foo != null) && (foo_Untyped != null))) { + "Only foo or foo_Untyped must be set, but not both" + } + require((foo != null) || (foo_Untyped != null)) { + "Either foo or foo_Untyped must be set, one of them is required" + } + } + + public constructor( + vararg pleaseUseNamedArguments: Unit, + foo: String? = null, + foo_Untyped: String? = null, + _customInputs: Map = mapOf(), + _customVersion: String? = null, + ) : this(foo = foo, foo_Untyped = foo_Untyped, _customInputs = _customInputs, _customVersion = _customVersion) + + @Suppress("SpreadOperator") + override fun toYamlArguments(): LinkedHashMap = linkedMapOf( + *listOfNotNull( + foo?.let { "foo" to it }, + foo_Untyped?.let { "foo" to it }, + *_customInputs.toList().toTypedArray(), + ).toTypedArray() + ) + + override fun buildOutputObject(stepId: String): Action.Outputs = Outputs(stepId) +} diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/GenerationTest.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/GenerationTest.kt index fa815974fa..cfb5b56c3e 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/GenerationTest.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/GenerationTest.kt @@ -582,6 +582,36 @@ class GenerationTest : binding.shouldContainAndMatchFile("ActionWithNoInputsWithMinorVersion_Untyped.kt") } } + + test("action with comment") { + // given + val actionManifest = + Metadata( + name = "Action with comment", + description = "Do something cool", + inputs = + mapOf( + "foo" to + Input( + description = "Short description", + required = true, + default = null, + ), + ), + ) + val coords = ActionCoords("john-smith", "action-with-comment", "v3", comment = "some-comment") + + // when + val binding = + coords.generateBinding( + metadataRevision = NewestForVersion, + metadata = actionManifest, + inputTypings = ActionTypings(inputTypings = actionManifest.allInputsAsStrings(), source = ACTION), + ) + + // then + binding.shouldContainAndMatchFile("ActionWithComment.kt") + } }) private fun Metadata.allInputsAsStrings(): Map = this.inputs.mapValues { StringTyping } diff --git a/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/actions/Action.kt b/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/actions/Action.kt index 1cc82bb42e..2114a3a48c 100644 --- a/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/actions/Action.kt +++ b/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/actions/Action.kt @@ -14,6 +14,8 @@ public abstract class Action { public open fun isCompatibleWithLibraryVersion(libraryVersion: String): Boolean = true + public val comment: String? = null + public open class Outputs( private val stepId: String, ) { diff --git a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt index 125f567649..e91da8d7aa 100644 --- a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt +++ b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt @@ -19,6 +19,10 @@ fun Parameters.extractActionCoords(extractVersion: Boolean): ActionCoords { .entries .find { "$it" == significantVersionString } } ?: FULL + val pinToCommit = nameAndPathAndSignificantVersionParts + .drop(1) + .takeIf { it.isNotEmpty() } + ?.single() == "commit_lenient" val nameAndPathParts = nameAndPath.split("__") val name = nameAndPathParts.first() val path = @@ -26,7 +30,22 @@ fun Parameters.extractActionCoords(extractVersion: Boolean): ActionCoords { .drop(1) .joinToString("/") .takeUnless { it.isBlank() } - val version = if (extractVersion) this["version"]!! else "irrelevant" + val version = if (extractVersion) { + val versionPart = this["version"]!! + if (pinToCommit) { + versionPart.split("__")[1] + } else { + versionPart + } + } else "irrelevant" + val comment = if (extractVersion) this["version"]!!.split("__")[0] else null - return ActionCoords(owner, name, version, significantVersion, path) + return ActionCoords( + owner = owner, + name = name, + version = version, + significantVersion = significantVersion, + path = path, + comment = comment, + ) } From cc12796b605d1859c0bc1269f39dd6f6ac1cf860 Mon Sep 17 00:00:00 2001 From: Piotr Krzeminski Date: Mon, 27 Oct 2025 20:27:47 +0100 Subject: [PATCH 2/9] ktlintify and regenerate API dumps --- .../api/action-binding-generator.api | 10 ++++---- .../api/github-workflows-kt.api | 1 + .../jitbindingserver/ActionCoords.kt | 24 +++++++++++-------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/action-binding-generator/api/action-binding-generator.api b/action-binding-generator/api/action-binding-generator.api index bced1be578..d8733d501a 100644 --- a/action-binding-generator/api/action-binding-generator.api +++ b/action-binding-generator/api/action-binding-generator.api @@ -1,14 +1,16 @@ public final class io/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;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 component4 ()Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion; public final fun component5 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;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;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;Ljava/lang/String;ILjava/lang/Object;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords; + public final fun component6 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;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;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;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 getComment ()Ljava/lang/String; public final fun getName ()Ljava/lang/String; public final fun getOwner ()Ljava/lang/String; public final fun getPath ()Ljava/lang/String; diff --git a/github-workflows-kt/api/github-workflows-kt.api b/github-workflows-kt/api/github-workflows-kt.api index 70c4d1334a..5f34704490 100644 --- a/github-workflows-kt/api/github-workflows-kt.api +++ b/github-workflows-kt/api/github-workflows-kt.api @@ -517,6 +517,7 @@ public final class io/github/typesafegithub/workflows/domain/Workflow : io/githu public abstract class io/github/typesafegithub/workflows/domain/actions/Action { public fun ()V public abstract fun buildOutputObject (Ljava/lang/String;)Lio/github/typesafegithub/workflows/domain/actions/Action$Outputs; + public final fun getComment ()Ljava/lang/String; public abstract fun getUsesString ()Ljava/lang/String; public final fun getYamlArgumentsString ()Ljava/lang/String; public fun isCompatibleWithLibraryVersion (Ljava/lang/String;)Z diff --git a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt index e91da8d7aa..bba4b1b65a 100644 --- a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt +++ b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt @@ -19,10 +19,11 @@ fun Parameters.extractActionCoords(extractVersion: Boolean): ActionCoords { .entries .find { "$it" == significantVersionString } } ?: FULL - val pinToCommit = nameAndPathAndSignificantVersionParts - .drop(1) - .takeIf { it.isNotEmpty() } - ?.single() == "commit_lenient" + val pinToCommit = + nameAndPathAndSignificantVersionParts + .drop(1) + .takeIf { it.isNotEmpty() } + ?.single() == "commit_lenient" val nameAndPathParts = nameAndPath.split("__") val name = nameAndPathParts.first() val path = @@ -30,14 +31,17 @@ fun Parameters.extractActionCoords(extractVersion: Boolean): ActionCoords { .drop(1) .joinToString("/") .takeUnless { it.isBlank() } - val version = if (extractVersion) { - val versionPart = this["version"]!! - if (pinToCommit) { - versionPart.split("__")[1] + val version = + if (extractVersion) { + val versionPart = this["version"]!! + if (pinToCommit) { + versionPart.split("__")[1] + } else { + versionPart + } } else { - versionPart + "irrelevant" } - } else "irrelevant" val comment = if (extractVersion) this["version"]!!.split("__")[0] else null return ActionCoords( From 3309dad00ec3110af899c604dc2315855f34a5f7 Mon Sep 17 00:00:00 2001 From: Piotr Krzeminski Date: Mon, 27 Oct 2025 20:35:44 +0100 Subject: [PATCH 3/9] Use comment only in RegularAction --- github-workflows-kt/api/github-workflows-kt.api | 16 ++++++++++------ .../workflows/domain/actions/Action.kt | 9 +++++++-- .../workflows/domain/actions/CustomAction.kt | 1 + 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/github-workflows-kt/api/github-workflows-kt.api b/github-workflows-kt/api/github-workflows-kt.api index 5f34704490..688b7269f7 100644 --- a/github-workflows-kt/api/github-workflows-kt.api +++ b/github-workflows-kt/api/github-workflows-kt.api @@ -517,7 +517,6 @@ public final class io/github/typesafegithub/workflows/domain/Workflow : io/githu public abstract class io/github/typesafegithub/workflows/domain/actions/Action { public fun ()V public abstract fun buildOutputObject (Ljava/lang/String;)Lio/github/typesafegithub/workflows/domain/actions/Action$Outputs; - public final fun getComment ()Ljava/lang/String; public abstract fun getUsesString ()Ljava/lang/String; public final fun getYamlArgumentsString ()Ljava/lang/String; public fun isCompatibleWithLibraryVersion (Ljava/lang/String;)Z @@ -530,19 +529,21 @@ public class io/github/typesafegithub/workflows/domain/actions/Action$Outputs { } public final class io/github/typesafegithub/workflows/domain/actions/CustomAction : io/github/typesafegithub/workflows/domain/actions/RegularAction { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun buildOutputObject (Ljava/lang/String;)Lio/github/typesafegithub/workflows/domain/actions/Action$Outputs; public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lio/github/typesafegithub/workflows/domain/actions/CustomAction; - public static synthetic fun copy$default (Lio/github/typesafegithub/workflows/domain/actions/CustomAction;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/github/typesafegithub/workflows/domain/actions/CustomAction; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Lio/github/typesafegithub/workflows/domain/actions/CustomAction; + public static synthetic fun copy$default (Lio/github/typesafegithub/workflows/domain/actions/CustomAction;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lio/github/typesafegithub/workflows/domain/actions/CustomAction; public fun equals (Ljava/lang/Object;)Z public fun getActionName ()Ljava/lang/String; public fun getActionOwner ()Ljava/lang/String; public fun getActionVersion ()Ljava/lang/String; + public fun getComment ()Ljava/lang/String; public final fun getInputs ()Ljava/util/Map; public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -602,9 +603,12 @@ public abstract class io/github/typesafegithub/workflows/domain/actions/LocalAct public abstract class io/github/typesafegithub/workflows/domain/actions/RegularAction : io/github/typesafegithub/workflows/domain/actions/Action { 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 fun getActionName ()Ljava/lang/String; public fun getActionOwner ()Ljava/lang/String; public fun getActionVersion ()Ljava/lang/String; + public fun getComment ()Ljava/lang/String; public fun getUsesString ()Ljava/lang/String; } diff --git a/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/actions/Action.kt b/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/actions/Action.kt index 2114a3a48c..e58c48285b 100644 --- a/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/actions/Action.kt +++ b/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/actions/Action.kt @@ -14,8 +14,6 @@ public abstract class Action { public open fun isCompatibleWithLibraryVersion(libraryVersion: String): Boolean = true - public val comment: String? = null - public open class Outputs( private val stepId: String, ) { @@ -27,7 +25,14 @@ public abstract class RegularAction( public open val actionOwner: String, public open val actionName: String, public open val actionVersion: String, + public open val comment: String? = null, ) : Action() { + public constructor( + actionOwner: String, + actionName: String, + actionVersion: String, + ) : this(actionOwner, actionName, actionVersion, null) + override val usesString: String get() = "$actionOwner/$actionName@$actionVersion" } diff --git a/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/actions/CustomAction.kt b/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/actions/CustomAction.kt index 18b74f489d..a7c679a3c8 100644 --- a/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/actions/CustomAction.kt +++ b/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/actions/CustomAction.kt @@ -12,6 +12,7 @@ public data class CustomAction( override val actionOwner: String, override val actionName: String, override val actionVersion: String, + override val comment: String? = null, public val inputs: Map = emptyMap(), ) : RegularAction(actionOwner, actionName, actionVersion) { override fun toYamlArguments(): LinkedHashMap = LinkedHashMap(inputs) From 8e64aa093aadbaa1b09caba05b576c9f6e435e7a Mon Sep 17 00:00:00 2001 From: Piotr Krzeminski Date: Mon, 27 Oct 2025 22:01:20 +0100 Subject: [PATCH 4/9] Add RegularAction constructor param --- .../actionbindinggenerator/generation/Generation.kt | 6 +++++- .../bindingsfromunittests/ActionWithComment.kt | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) 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 68e8e2880b..8f08c9fb9d 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 @@ -463,7 +463,11 @@ private fun TypeSpec.Builder.inheritsFromRegularAction( MINOR -> coords.version.minorVersion FULL -> coords.version }, - ) + ).also { + if (coords.comment != null) { + addSuperclassConstructorParameter("%S", coords.comment) + } + } } private val String.majorVersion get() = substringBefore('.') diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithComment.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithComment.kt index 33238a4081..00cbd5cb33 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithComment.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithComment.kt @@ -49,7 +49,7 @@ public data class ActionWithComment private constructor( * Allows overriding action's version, for example to use a specific minor version, or a newer version that the binding doesn't yet know about */ public val _customVersion: String? = null, -) : RegularAction("john-smith", "action-with-comment", _customVersion ?: "v3") { +) : RegularAction("john-smith", "action-with-comment", _customVersion ?: "v3", "some-comment") { init { require(!((foo != null) && (foo_Untyped != null))) { "Only foo or foo_Untyped must be set, but not both" From 1c224887fc9e31e9851449575899de2ac0d6d9b5 Mon Sep 17 00:00:00 2001 From: Piotr Krzeminski Date: Mon, 27 Oct 2025 22:54:50 +0100 Subject: [PATCH 5/9] Emit comment in YAML --- .../workflows/yaml/ObjectToYaml.kt | 19 ++++++++++ .../workflows/yaml/StepsToYaml.kt | 10 ++++- .../workflows/yaml/StringWithComment.kt | 6 +++ .../workflows/yaml/ObjectToYamlTest.kt | 20 ++++++++++ .../workflows/yaml/StepsToYamlTest.kt | 37 +++++++++++++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/StringWithComment.kt diff --git a/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/ObjectToYaml.kt b/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/ObjectToYaml.kt index cafcaa8cb5..319ec43498 100644 --- a/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/ObjectToYaml.kt +++ b/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/ObjectToYaml.kt @@ -2,9 +2,12 @@ package io.github.typesafegithub.workflows.yaml import it.krzeminski.snakeyaml.engine.kmp.api.DumpSettings import it.krzeminski.snakeyaml.engine.kmp.api.StreamDataWriter +import it.krzeminski.snakeyaml.engine.kmp.comments.CommentType +import it.krzeminski.snakeyaml.engine.kmp.comments.CommentType.IN_LINE import it.krzeminski.snakeyaml.engine.kmp.common.FlowStyle import it.krzeminski.snakeyaml.engine.kmp.common.ScalarStyle import it.krzeminski.snakeyaml.engine.kmp.emitter.Emitter +import it.krzeminski.snakeyaml.engine.kmp.events.CommentEvent import it.krzeminski.snakeyaml.engine.kmp.events.DocumentEndEvent import it.krzeminski.snakeyaml.engine.kmp.events.DocumentStartEvent import it.krzeminski.snakeyaml.engine.kmp.events.ImplicitTuple @@ -23,6 +26,7 @@ internal fun Any.toYaml(): String { // Otherwise line breaks appear in places that create an incorrect YAML, e.g. in the middle of GitHub // expressions. width = Int.MAX_VALUE, + dumpComments = true, ) val writer = object : StringWriter(), StreamDataWriter { @@ -46,6 +50,10 @@ private fun Any?.elementToYaml(emitter: Emitter) { is Map<*, *> -> this.mapToYaml(emitter) is List<*> -> this.listToYaml(emitter) is String, is Int, is Float, is Double, is Boolean, null -> this.scalarToYaml(emitter) + is StringWithComment -> { + this.value.scalarToYaml(emitter) + (" " + this.comment).commentToYaml(emitter) + } else -> error("Serializing $this is not supported!") } } @@ -96,3 +104,14 @@ private fun Any?.scalarToYaml(emitter: Emitter) { ScalarEvent(null, null, ImplicitTuple(true, true), this.toString(), scalarStyle), ) } + +private fun String.commentToYaml(emitter: Emitter) { + emitter.emit( + CommentEvent( + commentType = IN_LINE, + value = this, + startMark = null, + endMark = null, + ), + ) +} diff --git a/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/StepsToYaml.kt b/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/StepsToYaml.kt index 6d3e323760..93caee458c 100644 --- a/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/StepsToYaml.kt +++ b/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/StepsToYaml.kt @@ -12,6 +12,7 @@ import io.github.typesafegithub.workflows.domain.Shell.Pwsh import io.github.typesafegithub.workflows.domain.Shell.Python import io.github.typesafegithub.workflows.domain.Shell.Sh import io.github.typesafegithub.workflows.domain.Step +import io.github.typesafegithub.workflows.domain.actions.RegularAction internal fun List>.stepsToYaml(): List> = this.map { it.toYaml() } @@ -28,7 +29,14 @@ private fun ActionStep<*>.toYaml(): Map = "name" to name, "continue-on-error" to continueOnError, "timeout-minutes" to timeoutMinutes, - "uses" to action.usesString, + "uses" to + this.action.let { + if (it is RegularAction && it.comment != null) { + StringWithComment(it.usesString, it.comment!!) + } else { + it.usesString + } + }, "with" to action.toYamlArguments().ifEmpty { null }, "env" to env.ifEmpty { null }, "if" to condition, diff --git a/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/StringWithComment.kt b/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/StringWithComment.kt new file mode 100644 index 0000000000..f353969057 --- /dev/null +++ b/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/StringWithComment.kt @@ -0,0 +1,6 @@ +package io.github.typesafegithub.workflows.yaml + +internal data class StringWithComment( + val value: String, + val comment: String, +) diff --git a/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/yaml/ObjectToYamlTest.kt b/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/yaml/ObjectToYamlTest.kt index ec7a8b83b9..0f0a4eb637 100644 --- a/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/yaml/ObjectToYamlTest.kt +++ b/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/yaml/ObjectToYamlTest.kt @@ -140,4 +140,24 @@ class ObjectToYamlTest : """.trimIndent() } + + it("correctly serializes string with comment") { + // given + val objectToSerialize = + mapOf( + "foo" to "bar", + "baz" to StringWithComment("goo", "cool-comment"), + ) + + // when + val yaml = objectToSerialize.toYaml() + + // then + yaml shouldBe + """ + foo: 'bar' + baz: 'goo' # cool-comment + + """.trimIndent() + } }) diff --git a/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/yaml/StepsToYamlTest.kt b/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/yaml/StepsToYamlTest.kt index 476b13b2a0..cb3e6a6b76 100644 --- a/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/yaml/StepsToYamlTest.kt +++ b/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/yaml/StepsToYamlTest.kt @@ -8,7 +8,9 @@ import io.github.typesafegithub.workflows.actions.actions.UploadArtifact import io.github.typesafegithub.workflows.domain.ActionStep import io.github.typesafegithub.workflows.domain.CommandStep import io.github.typesafegithub.workflows.domain.Shell +import io.github.typesafegithub.workflows.domain.actions.Action import io.github.typesafegithub.workflows.domain.actions.CustomAction +import io.github.typesafegithub.workflows.domain.actions.RegularAction import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.shouldBe @@ -460,5 +462,40 @@ class StepsToYamlTest : ), ) } + + it("renders with comment") { + // given + val steps = + listOf( + ActionStep( + id = "someId", + action = + object : RegularAction( + "some-owner", + "some-name", + "some-version", + "some-comment", + ) { + override fun toYamlArguments(): LinkedHashMap = + linkedMapOf("foo" to "bar") + + override fun buildOutputObject(stepId: String): Outputs = Outputs(stepId) + }, + ), + ) + + // when + val yaml = steps.stepsToYaml() + + // then + yaml shouldBe + listOf( + mapOf( + "id" to "someId", + "uses" to StringWithComment("some-owner/some-name@some-version", "some-comment"), + "with" to mapOf("foo" to "bar"), + ), + ) + } } }) From 28a1ba15f009bcdd98f90ab9af544889f2fd3a99 Mon Sep 17 00:00:00 2001 From: Piotr Krzeminski Date: Mon, 27 Oct 2025 23:02:08 +0100 Subject: [PATCH 6/9] Add integration test --- .../workflows/IntegrationTest.kt | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/IntegrationTest.kt b/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/IntegrationTest.kt index b25c90160c..a8e5e72e5d 100644 --- a/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/IntegrationTest.kt +++ b/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/IntegrationTest.kt @@ -6,6 +6,9 @@ import io.github.typesafegithub.workflows.actions.endbug.AddAndCommit import io.github.typesafegithub.workflows.annotations.ExperimentalKotlinLogicStep import io.github.typesafegithub.workflows.domain.Concurrency import io.github.typesafegithub.workflows.domain.RunnerType +import io.github.typesafegithub.workflows.domain.actions.Action +import io.github.typesafegithub.workflows.domain.actions.Action.Outputs +import io.github.typesafegithub.workflows.domain.actions.RegularAction import io.github.typesafegithub.workflows.domain.triggers.Push import io.github.typesafegithub.workflows.dsl.expressions.expr import io.github.typesafegithub.workflows.dsl.workflow @@ -912,4 +915,57 @@ class IntegrationTest : """.trimIndent() targetTempFile.exists() shouldBe false } + + test("action version with comment") { + // when + workflow( + name = "Test workflow", + on = listOf(Push()), + sourceFile = sourceTempFile, + consistencyCheckJobConfig = Disabled, + ) { + job( + id = "test_job", + runsOn = RunnerType.UbuntuLatest, + ) { + uses( + name = "Check out", + action = + object : RegularAction( + "actions", + "checkout", + "08c6903cd8c0fde910a37f88322edcfb5dd907a8", + "v5.0.0", + ) { + override fun toYamlArguments(): LinkedHashMap = + linkedMapOf("path" to "my-repo") + + override fun buildOutputObject(stepId: String): Outputs = Outputs(stepId) + }, + ) + } + } + + // then + targetTempFile.readText() shouldBe + """ + # This file was generated using Kotlin DSL (.github/workflows/some_workflow.main.kts). + # If you want to modify the workflow, please change the Kotlin file and regenerate this YAML file. + # Generated with https://github.com/typesafegithub/github-workflows-kt + + name: 'Test workflow' + on: + push: {} + jobs: + test_job: + runs-on: 'ubuntu-latest' + steps: + - id: 'step-0' + name: 'Check out' + uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # v5.0.0 + with: + path: 'my-repo' + + """.trimIndent() + } }) From d75e718fb84d5e6a7145856e5a17adaa81426bea Mon Sep 17 00:00:00 2001 From: Piotr Krzeminski Date: Wed, 29 Oct 2025 13:03:48 +0100 Subject: [PATCH 7/9] Try fixing failing server tests --- .../typesafegithub/workflows/jitbindingserver/ActionCoords.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt index bba4b1b65a..659b7018a4 100644 --- a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt +++ b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt @@ -42,7 +42,7 @@ fun Parameters.extractActionCoords(extractVersion: Boolean): ActionCoords { } else { "irrelevant" } - val comment = if (extractVersion) this["version"]!!.split("__")[0] else null + val comment = if (pinToCommit) this["version"]!!.split("__")[0] else null return ActionCoords( owner = owner, From 82290237669d4b65279ceac9473d9731ba36d8e8 Mon Sep 17 00:00:00 2001 From: Piotr Krzeminski Date: Tue, 4 Nov 2025 10:09:23 +0100 Subject: [PATCH 8/9] Extend bindings server test --- ...st-script-consuming-jit-bindings.main.do-not-compile.kts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-script-consuming-jit-bindings.main.do-not-compile.kts b/.github/workflows/test-script-consuming-jit-bindings.main.do-not-compile.kts index 9f3d45b220..3edd61ac1d 100755 --- a/.github/workflows/test-script-consuming-jit-bindings.main.do-not-compile.kts +++ b/.github/workflows/test-script-consuming-jit-bindings.main.do-not-compile.kts @@ -1,6 +1,6 @@ #!/usr/bin/env kotlin @file:Repository("https://repo.maven.apache.org/maven2/") -@file:DependsOn("io.github.typesafegithub:github-workflows-kt:3.4.0") +@file:DependsOn("io.github.typesafegithub:github-workflows-kt:3.6.0") @file:DependsOn("io.kotest:kotest-assertions-core:5.9.1") @file:Repository("http://localhost:8080") @@ -24,6 +24,9 @@ // Always untyped action. @file:DependsOn("typesafegithub:always-untyped-action-for-tests:v1") +// Action version pinned to a commit. +@file:DependsOn("actions:setup-python___commit_lenient:v6.0.0__e797f83bcb11b83ae66e0230d6156d7c80228e7c") + import io.github.typesafegithub.workflows.actions.actions.Cache import io.github.typesafegithub.workflows.actions.actions.Checkout import io.github.typesafegithub.workflows.actions.actions.SetupNode @@ -41,6 +44,7 @@ println(AlwaysUntypedActionForTests_Untyped(foobar_Untyped = "baz")) println(ActionsSetupGradle()) println(Cache(path = listOf("some-path"), key = "some-key")) println(SetupNode()) +println(SetupPython()) ActionsDependencySubmission_Untyped().actionVersion shouldBe "v3" ActionsWrapperValidation().actionVersion shouldBe "v4.2" From b3da5b38e5bcef955aa5666d8d1eab3a76e3b816 Mon Sep 17 00:00:00 2001 From: Piotr Krzeminski Date: Tue, 4 Nov 2025 10:34:19 +0100 Subject: [PATCH 9/9] Add TODO describing the problem --- .../workflows/jitbindingserver/ArtifactRoutes.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutes.kt b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutes.kt index 553e969ee9..2b8d0bbe2d 100644 --- a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutes.kt +++ b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutes.kt @@ -84,6 +84,9 @@ private fun Route.getArtifact( refresh: Boolean, ) { get { + // TODO: there'a problem with caching the artifacts under certain keys, + // and parameters.extractActionCoords returning certain (different) ActionCoords. + // Because of this problem, the artifact that uses commit pinning is now not found. val bindingArtifacts = call.toBindingArtifacts(refresh, bindingsCache) ?: return@get call.respondNotFound() if (refresh && !deliverOnRefreshRoute) return@get call.respondText(text = "OK")