├── .gitattributes
├── .github
├── FUNDING.yml
├── workflows
│ ├── test-gradle-project-using-bindings-server
│ │ ├── settings.gradle.kts
│ │ ├── gradle
│ │ │ └── wrapper
│ │ │ │ ├── gradle-wrapper.jar
│ │ │ │ └── gradle-wrapper.properties
│ │ ├── build.gradle.kts
│ │ └── src
│ │ │ └── main
│ │ │ └── kotlin
│ │ │ └── Main.kt
│ ├── test-served-bindings-depend-on-library.main.do-not-compile.kts
│ ├── test-local-action
│ │ └── action.yml
│ ├── setup-python.main.kts
│ ├── setup-java.main.kts
│ ├── _shared.main.kts
│ ├── end-to-end-tests-2nd-workflow.yaml
│ └── test-script-consuming-jit-bindings.main.do-not-compile.kts
├── semantic.yml
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── core-feature-request.md
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── docs
├── requirements.txt
├── user-guide
│ ├── job-outputs.md
│ ├── nightly-builds.md
│ ├── compensating-librarys-missing-features.md
│ └── getting_started.md
├── projects-using-this-library.md
├── index.md
├── Logo.svg
└── feature-coverage.md
├── renovate.json
├── code-generator
├── src
│ ├── main
│ │ ├── resources
│ │ │ └── META-INF
│ │ │ │ └── services
│ │ │ │ └── com.google.devtools.ksp.processing.SymbolProcessorProvider
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── typesafegithub
│ │ │ └── workflows
│ │ │ ├── codegenerator
│ │ │ ├── KspGeneratorProvider.kt
│ │ │ └── KspGenerator.kt
│ │ │ ├── dsl
│ │ │ └── expressions
│ │ │ │ └── TextUtils.kt
│ │ │ └── VersionInfo.kt
│ └── test
│ │ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── typesafegithub
│ │ └── workflows
│ │ └── dsl
│ │ └── expressions
│ │ └── TextUtilsTest.kt
└── build.gradle.kts
├── github-workflows-kt
├── src
│ ├── main
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── typesafegithub
│ │ │ └── workflows
│ │ │ ├── dsl
│ │ │ ├── GithubActionsDsl.kt
│ │ │ ├── HasCustomArguments.kt
│ │ │ ├── expressions
│ │ │ │ ├── contexts
│ │ │ │ │ ├── VarsContext.kt
│ │ │ │ │ ├── RunnerContext.kt
│ │ │ │ │ ├── SecretsContext.kt
│ │ │ │ │ └── FunctionsContext.kt
│ │ │ │ ├── Expression.kt
│ │ │ │ ├── ExpressionContext.kt
│ │ │ │ └── Contexts.kt
│ │ │ └── Container.kt
│ │ │ ├── yaml
│ │ │ ├── StringWithComment.kt
│ │ │ ├── Utils.kt
│ │ │ ├── Preamble.kt
│ │ │ ├── Case.kt
│ │ │ ├── ContextMapping.kt
│ │ │ ├── ContainerToYaml.kt
│ │ │ ├── StepsToYaml.kt
│ │ │ └── ConsistencyCheckJobConfig.kt
│ │ │ ├── annotations
│ │ │ └── ExperimentalKotlinLogicStep.kt
│ │ │ ├── domain
│ │ │ ├── triggers
│ │ │ │ ├── Trigger.kt
│ │ │ │ ├── CheckRun.kt
│ │ │ │ ├── Create.kt
│ │ │ │ ├── Delete.kt
│ │ │ │ ├── Status.kt
│ │ │ │ ├── CheckSuite.kt
│ │ │ │ ├── Discussion.kt
│ │ │ │ ├── DiscussionComment.kt
│ │ │ │ ├── BranchProtectionRule.kt
│ │ │ │ ├── Fork.kt
│ │ │ │ ├── Gollum.kt
│ │ │ │ ├── Issues.kt
│ │ │ │ ├── Label.kt
│ │ │ │ ├── Watch.kt
│ │ │ │ ├── Project.kt
│ │ │ │ ├── Milestone.kt
│ │ │ │ ├── PageBuild.kt
│ │ │ │ ├── Deployment.kt
│ │ │ │ ├── MergeGroup.kt
│ │ │ │ ├── ProjectCard.kt
│ │ │ │ ├── IssueComment.kt
│ │ │ │ ├── PublicWorkflow.kt
│ │ │ │ ├── WorkflowRun.kt
│ │ │ │ ├── ProjectColumn.kt
│ │ │ │ ├── RegistryPackage.kt
│ │ │ │ ├── DeploymentStatus.kt
│ │ │ │ ├── PullRequestReview.kt
│ │ │ │ ├── Release.kt
│ │ │ │ ├── PullRequestReviewComment.kt
│ │ │ │ ├── RepositoryDispatch.kt
│ │ │ │ ├── Push.kt
│ │ │ │ ├── WorkflowDispatch.kt
│ │ │ │ ├── WorkflowCall.kt
│ │ │ │ ├── PullRequestTarget.kt
│ │ │ │ └── PullRequest.kt
│ │ │ ├── contexts
│ │ │ │ ├── Contexts.kt
│ │ │ │ ├── OutputsContext.kt
│ │ │ │ └── GithubContext.kt
│ │ │ ├── Environment.kt
│ │ │ ├── Shell.kt
│ │ │ ├── Concurrency.kt
│ │ │ ├── AbstractResult.kt
│ │ │ ├── JobOutputs.kt
│ │ │ ├── Job.kt
│ │ │ ├── actions
│ │ │ │ ├── CustomAction.kt
│ │ │ │ └── Action.kt
│ │ │ ├── Workflow.kt
│ │ │ └── RunnerType.kt
│ │ │ ├── internal
│ │ │ ├── PathUtils.kt
│ │ │ ├── InternalGithubActionsApi.kt
│ │ │ └── CaseEnumSerializer.kt
│ │ │ └── validation
│ │ │ └── Values.kt
│ └── test
│ │ ├── resources
│ │ ├── contexts
│ │ │ ├── github-required-fields.json
│ │ │ └── github-all-fields.json
│ │ └── payloads
│ │ │ └── runner.json
│ │ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── typesafegithub
│ │ └── workflows
│ │ ├── JavaBytecodeVersionTest.kt
│ │ ├── domain
│ │ ├── RunnerTypeTest.kt
│ │ ├── triggers
│ │ │ ├── PullRequestTest.kt
│ │ │ ├── PullRequestTargetTest.kt
│ │ │ └── PushTest.kt
│ │ └── StepTest.kt
│ │ ├── actions
│ │ └── CustomActionTest.kt
│ │ ├── dsl
│ │ └── expressions
│ │ │ └── PayloadTest.kt
│ │ ├── yaml
│ │ ├── CaseTest.kt
│ │ └── ContextMappingTest.kt
│ │ └── docsnippets
│ │ ├── GettingStartedSnippets.kt
│ │ └── CompensatingLibrarysMissingFeaturesSnippets.kt
└── build.gradle.kts
├── .idea
├── externalDependencies.xml
└── icon.svg
├── action-binding-generator
├── src
│ ├── main
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── typesafegithub
│ │ │ └── workflows
│ │ │ └── actionbindinggenerator
│ │ │ ├── domain
│ │ │ ├── TypingActualSource.kt
│ │ │ ├── MetadataRevision.kt
│ │ │ ├── ActionTypings.kt
│ │ │ ├── SignificantVersion.kt
│ │ │ └── ActionCoords.kt
│ │ │ ├── metadata
│ │ │ ├── InputNullability.kt
│ │ │ └── MetadataReading.kt
│ │ │ ├── generation
│ │ │ └── ClassNaming.kt
│ │ │ ├── utils
│ │ │ └── TextUtils.kt
│ │ │ └── typing
│ │ │ ├── Typing.kt
│ │ │ └── ActionTypes.kt
│ └── test
│ │ ├── kotlin
│ │ └── io
│ │ │ └── github
│ │ │ └── typesafegithub
│ │ │ └── workflows
│ │ │ └── actionbindinggenerator
│ │ │ ├── HttpTestingUtils.kt
│ │ │ ├── bindingsfromunittests
│ │ │ ├── ActionWithOutputsTest.kt
│ │ │ ├── ActionWithSomeOptionalInputsTest.kt
│ │ │ ├── SimpleActionWithRequiredStringInputsTest.kt
│ │ │ ├── ActionWithNoInputs.kt
│ │ │ ├── ActionWithSubAction.kt
│ │ │ ├── ActionWithNoInputsWithMajorVersion.kt
│ │ │ ├── ActionWithNoInputsWithMinorVersion.kt
│ │ │ └── ActionWithNoInputsFromFallbackVersion.kt
│ │ │ ├── generation
│ │ │ └── ClassNamingTest.kt
│ │ │ ├── Utils.kt
│ │ │ ├── metadata
│ │ │ └── InputNullabilityTest.kt
│ │ │ └── utils
│ │ │ └── TextUtilsTest.kt
│ │ └── resources
│ │ └── types
│ │ ├── action-types.yml
│ │ └── action.yml
└── build.gradle.kts
├── test-utils
├── build.gradle.kts
└── src
│ └── main
│ └── kotlin
│ └── io
│ └── github
│ └── typesafegithub
│ └── workflows
│ └── testutils
│ ├── JdkVersionToBytecodeVersion.kt
│ └── JavaBytecodeUtils.kt
├── action-updates-checker
├── api
│ └── action-updates-checker.api
├── build.gradle.kts
└── src
│ └── main
│ └── kotlin
│ └── io
│ └── github
│ └── typesafegithub
│ └── workflows
│ └── updates
│ └── model
│ └── RegularActionVersions.kt
├── gradle.properties
├── shared-internal
├── src
│ └── main
│ │ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── typesafegithub
│ │ └── workflows
│ │ └── shared
│ │ └── internal
│ │ ├── PathUtils.kt
│ │ ├── GithubUtils.kt
│ │ ├── model
│ │ └── Version.kt
│ │ └── GitHubApp.kt
└── build.gradle.kts
├── .editorconfig
├── .gitignore
├── maven-binding-builder
├── build.gradle.kts
└── src
│ ├── main
│ └── kotlin
│ │ └── io
│ │ └── github
│ │ └── typesafegithub
│ │ └── workflows
│ │ └── mavenbinding
│ │ ├── ActionCoordsUtils.kt
│ │ ├── Checksums.kt
│ │ ├── PackageArtifactsBuilding.kt
│ │ ├── PomBuilding.kt
│ │ └── MavenMetadataBuilding.kt
│ └── test
│ └── kotlin
│ └── io
│ └── github
│ └── typesafegithub
│ └── workflows
│ └── mavenbinding
│ └── JavaBytecodeVersionTest.kt
├── jit-binding-server
├── src
│ └── main
│ │ ├── kotlin
│ │ └── io
│ │ │ └── github
│ │ │ └── typesafegithub
│ │ │ └── workflows
│ │ │ └── jitbindingserver
│ │ │ ├── InternalRoutes.kt
│ │ │ ├── Plugins.kt
│ │ │ ├── ActionCoords.kt
│ │ │ └── MetadataRoutes.kt
│ │ └── resources
│ │ └── log4j2.xml
└── build.gradle.kts
├── settings.gradle.kts
├── CONTRIBUTING.md
├── MAINTENANCE.md
├── mkdocs.yml
└── gradlew.bat
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
3 | *.bat text eol=crlf
4 | *.jar binary
5 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: krzema12
2 | custom: https://www.buymeacoffee.com/krzema12
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/typesafegithub/github-workflows-kt/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.github/workflows/test-gradle-project-using-bindings-server/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "test-gradle-project-using-bindings-server"
2 |
3 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | mkdocs==1.6.1
2 | mkdocs-material==9.7.0
3 | mkdocs-material-extensions==1.3.1
4 | mkdocs-video==1.5.0
5 | pymdown-extensions==10.19.1
6 |
--------------------------------------------------------------------------------
/.github/semantic.yml:
--------------------------------------------------------------------------------
1 | # Semantic Commit bot: https://github.com/Ezard/semantic-prs
2 |
3 | # Always validate the PR title, and ignore the commits
4 | titleOnly: true
5 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:recommended"
5 | ],
6 | "automerge": true
7 | }
8 |
--------------------------------------------------------------------------------
/code-generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider:
--------------------------------------------------------------------------------
1 | io.github.typesafegithub.workflows.codegenerator.KspGeneratorProvider
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/dsl/GithubActionsDsl.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.dsl
2 |
3 | @DslMarker
4 | internal annotation class GithubActionsDsl
5 |
--------------------------------------------------------------------------------
/.idea/externalDependencies.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/test/resources/contexts/github-required-fields.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": "some-owner/some-repo",
3 | "sha": "db76dd0f1149901e1cdf60ec98d568b32fa7eb71",
4 | "event": {},
5 | "event_name": "push"
6 | }
--------------------------------------------------------------------------------
/.github/workflows/test-gradle-project-using-bindings-server/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/typesafegithub/github-workflows-kt/HEAD/.github/workflows/test-gradle-project-using-bindings-server/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/StringWithComment.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.yaml
2 |
3 | internal data class StringWithComment(
4 | val value: String,
5 | val comment: String,
6 | )
7 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/annotations/ExperimentalKotlinLogicStep.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.annotations
2 |
3 | @Target(AnnotationTarget.FUNCTION)
4 | @RequiresOptIn
5 | public annotation class ExperimentalKotlinLogicStep
6 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/Trigger.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import io.github.typesafegithub.workflows.dsl.HasCustomArguments
4 |
5 | public sealed class Trigger : HasCustomArguments
6 |
--------------------------------------------------------------------------------
/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.actionbindinggenerator.domain
2 |
3 | public enum class TypingActualSource {
4 | ACTION,
5 | TYPING_CATALOG,
6 | }
7 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/test/resources/payloads/runner.json:
--------------------------------------------------------------------------------
1 | {
2 | "os": "Linux",
3 | "arch": "X64",
4 | "name": "GitHub Actions 2",
5 | "tool_cache": "/opt/hostedtoolcache",
6 | "temp": "/home/runner/work/_temp",
7 | "workspace": "/home/runner/work/github-actions-kotlin-dsl"
8 | }
9 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/contexts/Contexts.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.contexts
2 |
3 | public data class Contexts(
4 | val github: GithubContext,
5 | ) {
6 | val outputs: OutputsContext = OutputsContext
7 | }
8 |
--------------------------------------------------------------------------------
/test-utils/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | buildsrc.convention.`kotlin-jvm`
3 | }
4 |
5 | dependencies {
6 | implementation(platform("io.kotest:kotest-bom:6.0.7"))
7 | implementation("io.kotest:kotest-assertions-core")
8 | implementation("io.kotest:kotest-common")
9 | }
10 |
--------------------------------------------------------------------------------
/.github/workflows/test-served-bindings-depend-on-library.main.do-not-compile.kts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env kotlin
2 | @file:Repository("https://repo.maven.apache.org/maven2/")
3 | @file:Repository("http://localhost:8080")
4 | @file:DependsOn("actions:checkout:v4")
5 |
6 | import io.github.typesafegithub.workflows.actions.actions.Checkout
7 |
8 | Checkout()
9 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/Environment.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain
2 |
3 | /**
4 | * https://docs.github.com/en/actions/using-jobs/using-environments-for-jobs
5 | */
6 | public data class Environment(
7 | val name: String,
8 | val url: String? = null,
9 | )
10 |
--------------------------------------------------------------------------------
/test-utils/src/main/kotlin/io/github/typesafegithub/workflows/testutils/JdkVersionToBytecodeVersion.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.testutils
2 |
3 | /**
4 | * See https://javaalmanac.io/bytecode/versions/
5 | */
6 | object JdkVersionToBytecodeVersion {
7 | const val JDK_8 = "52.0"
8 | const val JDK_11 = "55.0"
9 | }
10 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/internal/PathUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.internal
2 |
3 | import java.nio.file.Path
4 | import kotlin.io.path.absolute
5 | import kotlin.io.path.relativeTo
6 |
7 | internal fun Path.relativeToAbsolute(base: Path): Path = absolute().relativeTo(base.absolute())
8 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/test/resources/contexts/github-all-fields.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": "some-owner/some-repo",
3 | "sha": "db76dd0f1149901e1cdf60ec98d568b32fa7eb71",
4 | "ref": "refs/heads/main",
5 | "base_ref": "refs/heads/develop",
6 | "event": {
7 | "after": "1383af4847629428f1675f5c2e81e67cc3a4efb0"
8 | },
9 | "event_name": "push"
10 | }
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/Utils.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.yaml
2 |
3 | @Suppress("UNCHECKED_CAST", "SpreadOperator")
4 | internal fun mapOfNotNullValues(vararg pairs: Pair): Map =
5 | mapOf(*(pairs.filter { it.second != null } as List>).toTypedArray())
6 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/dsl/HasCustomArguments.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.dsl
2 |
3 | import kotlinx.serialization.Contextual
4 |
5 | public interface HasCustomArguments {
6 | @Suppress("ktlint:standard:backing-property-naming", "VariableNaming")
7 | public val _customArguments: Map
8 | }
9 |
--------------------------------------------------------------------------------
/.github/workflows/test-local-action/action.yml:
--------------------------------------------------------------------------------
1 | name: Test local action
2 | description: I'm a simple local action used in integration tests!
3 | inputs:
4 | name:
5 | description: Name to be used in the greeting.
6 | required: true
7 | runs:
8 | using: "composite"
9 | steps:
10 | - name: Print greeting
11 | shell: bash
12 | run: echo 'Hello ${{ inputs.name }}!'
13 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionSha256Sum=72f44c9f8ebcb1af43838f45ee5c4aa9c5444898b3468ab3f4af7b6076c5bc3f
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
5 | networkTimeout=10000
6 | validateDistributionUrl=true
7 | zipStoreBase=GRADLE_USER_HOME
8 | zipStorePath=wrapper/dists
9 |
--------------------------------------------------------------------------------
/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.actionbindinggenerator.domain
2 |
3 | public sealed interface MetadataRevision
4 |
5 | public data object NewestForVersion : MetadataRevision
6 |
7 | public data class CommitHash(
8 | val value: String,
9 | ) : MetadataRevision
10 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/CheckRun.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | public data class CheckRun(
8 | override val _customArguments: Map = mapOf(),
9 | ) : Trigger()
10 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/Create.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | public data class Create(
8 | override val _customArguments: Map = mapOf(),
9 | ) : Trigger()
10 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/Delete.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | public data class Delete(
8 | override val _customArguments: Map = mapOf(),
9 | ) : Trigger()
10 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/Status.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | public data class Status(
8 | override val _customArguments: Map = mapOf(),
9 | ) : Trigger()
10 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/CheckSuite.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | public data class CheckSuite(
8 | override val _customArguments: Map = mapOf(),
9 | ) : Trigger()
10 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/Discussion.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | public data class Discussion(
8 | override val _customArguments: Map = mapOf(),
9 | ) : Trigger()
10 |
--------------------------------------------------------------------------------
/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/HttpTestingUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.actionbindinggenerator
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.engine.mock.MockEngine
5 | import io.ktor.client.engine.mock.respond
6 |
7 | fun mockClientReturning(response: String) = HttpClient(MockEngine { respond(content = response) })
8 |
--------------------------------------------------------------------------------
/action-updates-checker/api/action-updates-checker.api:
--------------------------------------------------------------------------------
1 | public final class io/github/typesafegithub/workflows/updates/ReportingKt {
2 | public static final fun reportAvailableUpdates (Lio/github/typesafegithub/workflows/domain/Workflow;ZLjava/lang/String;)V
3 | public static synthetic fun reportAvailableUpdates$default (Lio/github/typesafegithub/workflows/domain/Workflow;ZLjava/lang/String;ILjava/lang/Object;)V
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/DiscussionComment.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | public data class DiscussionComment(
8 | override val _customArguments: Map = mapOf(),
9 | ) : Trigger()
10 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/BranchProtectionRule.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | public data class BranchProtectionRule(
8 | override val _customArguments: Map = mapOf(),
9 | ) : Trigger()
10 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/contexts/OutputsContext.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.contexts
2 |
3 | import java.io.File
4 |
5 | public object OutputsContext {
6 | public operator fun set(
7 | outputName: String,
8 | value: String,
9 | ) {
10 | File(System.getenv("GITHUB_OUTPUT")).appendText("$outputName=$value")
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/code-generator/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | buildsrc.convention.`kotlin-jvm`
3 |
4 | kotlin("plugin.serialization")
5 | }
6 |
7 | dependencies {
8 | implementation("com.google.devtools.ksp:symbol-processing-api:2.3.3")
9 | implementation("com.squareup:kotlinpoet:2.2.0")
10 | implementation("com.squareup:kotlinpoet-ksp:2.2.0")
11 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
12 | }
13 |
--------------------------------------------------------------------------------
/.github/workflows/test-gradle-project-using-bindings-server/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionSha256Sum=72f44c9f8ebcb1af43838f45ee5c4aa9c5444898b3468ab3f4af7b6076c5bc3f
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
5 | networkTimeout=10000
6 | validateDistributionUrl=true
7 | zipStoreBase=GRADLE_USER_HOME
8 | zipStorePath=wrapper/dists
9 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8
2 | org.gradle.caching=true
3 | org.gradle.configuration-cache=true
4 |
5 | org.gradle.java.installations.auto-detect=true
6 | org.gradle.java.installations.auto-download=false
7 |
8 | kotlin.code.style=official
9 |
10 | # So that KSP doesn't generate files in "test" sources root
11 | ksp.allow.all.target.configuration=false
12 |
13 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
14 |
--------------------------------------------------------------------------------
/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionTypings.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.actionbindinggenerator.domain
2 |
3 | import io.github.typesafegithub.workflows.actionbindinggenerator.typing.Typing
4 |
5 | public data class ActionTypings(
6 | val inputTypings: Map = emptyMap(),
7 | val source: TypingActualSource? = null,
8 | val fromFallbackVersion: Boolean = false,
9 | )
10 |
--------------------------------------------------------------------------------
/shared-internal/src/main/kotlin/io/github/typesafegithub/workflows/shared/internal/PathUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.shared.internal
2 |
3 | import java.nio.file.Path
4 | import kotlin.io.path.absolute
5 | import kotlin.io.path.exists
6 |
7 | fun Path.findGitRoot(): Path =
8 | generateSequence(this.absolute()) { it.parent }
9 | .firstOrNull { it.resolve(".git").exists() }
10 | ?: error("could not find a git root from ${this.absolute()}")
11 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/Fork.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#fork
8 | */
9 | @Serializable
10 | public data class Fork(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [{*.kt, *.kts}]
4 | charset = utf-8
5 | max_line_length = 120
6 | end_of_line = lf
7 | indent_size = 4
8 | indent_style = space
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 | ij_kotlin_allow_trailing_comma = true
12 | ij_kotlin_allow_trailing_comma_on_call_site = true
13 | ij_kotlin_packages_to_use_import_on_demand = unset
14 | ij_kotlin_name_count_to_use_star_import = 2147483647
15 | ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
16 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/Gollum.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#gollum
8 | */
9 | @Serializable
10 | public data class Gollum(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/Issues.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issues
8 | */
9 | @Serializable
10 | public data class Issues(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/Label.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#label
8 | */
9 | @Serializable
10 | public data class Label(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/Watch.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#watch
8 | */
9 | @Serializable
10 | public data class Watch(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/Project.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#project
8 | */
9 | @Serializable
10 | public data class Project(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/Shell.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain
2 |
3 | public sealed interface Shell {
4 | public data class Custom(
5 | val value: String,
6 | ) : Shell
7 |
8 | public object Bash : Shell
9 |
10 | public object Cmd : Shell
11 |
12 | public object Pwsh : Shell
13 |
14 | public object PowerShell : Shell
15 |
16 | public object Python : Shell
17 |
18 | public object Sh : Shell
19 | }
20 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/Milestone.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#milestone
8 | */
9 | @Serializable
10 | public data class Milestone(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/PageBuild.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#page_build
8 | */
9 | @Serializable
10 | public data class PageBuild(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/Deployment.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#deployment
8 | */
9 | @Serializable
10 | public data class Deployment(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/MergeGroup.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#merge_group
8 | */
9 | @Serializable
10 | public data class MergeGroup(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/ProjectCard.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#project
8 | */
9 | @Serializable
10 | public data class ProjectCard(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/Preamble.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.yaml
2 |
3 | public sealed class Preamble(
4 | public val content: String,
5 | ) {
6 | public class Just(
7 | content: String,
8 | ) : Preamble(content)
9 |
10 | public class WithOriginalBefore(
11 | content: String,
12 | ) : Preamble(content)
13 |
14 | public class WithOriginalAfter(
15 | content: String,
16 | ) : Preamble(content)
17 | }
18 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/IssueComment.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issue_comment
8 | */
9 | @Serializable
10 | public data class IssueComment(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/PublicWorkflow.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#public
8 | */
9 | @Serializable
10 | public data class PublicWorkflow(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/WorkflowRun.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_run
8 | */
9 | @Serializable
10 | public data class WorkflowRun(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 |
3 | .idea
4 | !.idea/externalDependencies.xml # Required IntelliJ plugins.
5 | !.idea/icon.svg
6 | .DS_Store
7 | .kotlin
8 |
9 | build
10 |
11 | # Created in unit tests - easier and more readable to ignore it rather than adjust the test.
12 | /.github/workflows/.yaml
13 | .github/workflows/some_workflow.yaml
14 |
15 | # Action bindings are generated by CI each time, and locally we should regenerate it as often as possible.
16 | /.github/workflows/generated
17 |
18 | /jit-binding-server/logs/
19 | /logs/
20 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/ProjectColumn.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#project_column
8 | */
9 | @Serializable
10 | public data class ProjectColumn(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/validation/Values.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.validation
2 |
3 | internal fun requireMatchesRegex(
4 | field: String,
5 | value: String,
6 | regex: Regex,
7 | url: String?,
8 | ) {
9 | require(regex.matchEntire(value) != null) {
10 | val seeUrl = if (url.isNullOrBlank()) "" else "\nSee: $url"
11 | """Invalid field ${field.replace('.', '(')}="$value") does not match regex: $regex$seeUrl"""
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/RegistryPackage.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#registry_package
8 | */
9 | @Serializable
10 | public data class RegistryPackage(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/.github/workflows/setup-python.main.kts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env kotlin
2 | @file:Repository("https://repo.maven.apache.org/maven2/")
3 | @file:DependsOn("io.github.typesafegithub:github-workflows-kt:3.7.0")
4 |
5 | @file:Repository("https://bindings.krzeminski.it")
6 | @file:DependsOn("actions:setup-python:v6")
7 |
8 | import io.github.typesafegithub.workflows.actions.actions.SetupPython
9 | import io.github.typesafegithub.workflows.dsl.JobBuilder
10 |
11 | fun JobBuilder<*>.setupPython() =
12 | uses(action = SetupPython(pythonVersion = "3.14"))
13 |
--------------------------------------------------------------------------------
/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/metadata/InputNullability.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.actionbindinggenerator.metadata
2 |
3 | /**
4 | * [Input.required] is in theory a required field in action's metadata, but in practice a lot of actions don't specify
5 | * it. It's thus a challenge to infer nullability of inputs in the Kotlin bindings. This function tackles this task.
6 | */
7 | internal fun Input.shouldBeRequiredInBinding() = default == null && required == true
8 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/DeploymentStatus.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#deployment_status
8 | */
9 | @Serializable
10 | public data class DeploymentStatus(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/PullRequestReview.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_review
8 | */
9 | @Serializable
10 | public data class PullRequestReview(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/ClassNaming.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.actionbindinggenerator.generation
2 |
3 | import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords
4 | import io.github.typesafegithub.workflows.actionbindinggenerator.domain.fullName
5 | import io.github.typesafegithub.workflows.actionbindinggenerator.utils.toPascalCase
6 |
7 | internal fun ActionCoords.buildActionClassName(): String = this.fullName.toPascalCase()
8 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/Release.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#release
8 | */
9 | @Serializable
10 | public data class Release(
11 | val types: List? = null,
12 | override val _customArguments: Map = mapOf(),
13 | ) : Trigger()
14 |
--------------------------------------------------------------------------------
/action-updates-checker/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | buildsrc.convention.`kotlin-jvm`
3 | buildsrc.convention.publishing
4 |
5 | id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.18.1"
6 | }
7 |
8 | group = rootProject.group
9 | version = rootProject.version
10 |
11 | dependencies {
12 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
13 |
14 | implementation(projects.githubWorkflowsKt)
15 | implementation(projects.sharedInternal)
16 | }
17 |
18 | kotlin {
19 | explicitApi()
20 | }
21 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/internal/InternalGithubActionsApi.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.internal
2 |
3 | /**
4 | * Marks declarations that are **internal** to github-workflows-kt,
5 | * which means they should not be used outside github-workflows-kt modules,
6 | * because their signatures and semantics can **(will) change** between future releases,
7 | * without any warnings and without providing any migration aids.
8 | */
9 | @RequiresOptIn
10 | public annotation class InternalGithubActionsApi
11 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/PullRequestReviewComment.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_review_comment
8 | */
9 | @Serializable
10 | public data class PullRequestReviewComment(
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger()
13 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/RepositoryDispatch.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#repository_dispatch
8 | */
9 | @Serializable
10 | public data class RepositoryDispatch(
11 | val types: List? = null,
12 | override val _customArguments: Map = mapOf(),
13 | ) : Trigger()
14 |
--------------------------------------------------------------------------------
/maven-binding-builder/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | buildsrc.convention.`kotlin-jvm`
3 | }
4 |
5 | dependencies {
6 | implementation("org.jetbrains.kotlin:kotlin-compiler")
7 | api("io.arrow-kt:arrow-core:2.2.0")
8 | api("io.ktor:ktor-client-core:3.3.3")
9 | api(projects.actionBindingGenerator)
10 | implementation(projects.sharedInternal)
11 | implementation("io.github.oshai:kotlin-logging:7.0.13")
12 |
13 | runtimeOnly(projects.githubWorkflowsKt)
14 |
15 | testImplementation("io.ktor:ktor-client-cio:3.3.3")
16 | testImplementation(projects.testUtils)
17 | }
18 |
--------------------------------------------------------------------------------
/action-updates-checker/src/main/kotlin/io/github/typesafegithub/workflows/updates/model/RegularActionVersions.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.updates.model
2 |
3 | import io.github.typesafegithub.workflows.domain.ActionStep
4 | import io.github.typesafegithub.workflows.domain.actions.RegularAction
5 | import io.github.typesafegithub.workflows.shared.internal.model.Version
6 |
7 | internal data class RegularActionVersions(
8 | val action: RegularAction<*>,
9 | val steps: List>,
10 | val newerVersions: List,
11 | val availableVersions: List,
12 | )
13 |
--------------------------------------------------------------------------------
/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/ActionCoordsUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.mavenbinding
2 |
3 | import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords
4 | import io.github.typesafegithub.workflows.actionbindinggenerator.domain.SignificantVersion.FULL
5 | import io.github.typesafegithub.workflows.actionbindinggenerator.domain.subName
6 |
7 | internal val ActionCoords.mavenName: String get() = "$name${subName.replace("/", "__")}${
8 | significantVersion.takeUnless { it == FULL }?.let { "___$it" } ?: ""
9 | }"
10 |
--------------------------------------------------------------------------------
/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/InternalRoutes.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.jitbindingserver
2 |
3 | import io.ktor.server.response.respondText
4 | import io.ktor.server.routing.Routing
5 | import io.ktor.server.routing.get
6 | import io.micrometer.prometheusmetrics.PrometheusMeterRegistry
7 |
8 | fun Routing.internalRoutes(prometheusRegistry: PrometheusMeterRegistry) {
9 | get("/metrics") {
10 | call.respondText(text = prometheusRegistry.scrape())
11 | }
12 |
13 | get("/status") {
14 | call.respondText(text = "OK")
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/contexts/GithubContext.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("ConstructorParameterNaming")
2 |
3 | package io.github.typesafegithub.workflows.domain.contexts
4 |
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | public data class GithubContext(
9 | val repository: String,
10 | val sha: String,
11 | val ref: String? = null,
12 | val base_ref: String? = null,
13 | val event: GithubContextEvent,
14 | val event_name: String,
15 | )
16 |
17 | @Serializable
18 | public data class GithubContextEvent(
19 | val after: String? = null,
20 | )
21 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/dsl/expressions/contexts/VarsContext.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.dsl.expressions.contexts
2 |
3 | import io.github.typesafegithub.workflows.dsl.expressions.ExpressionContext
4 |
5 | /**
6 | * Vars
7 | *
8 | * Vars allow you to store configuration variables in your organization, repository, or repository environments.
9 | *
10 | * https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#vars-context
11 | */
12 | public object VarsContext : ExpressionContext("vars")
13 |
--------------------------------------------------------------------------------
/action-binding-generator/src/test/resources/types/action-types.yml:
--------------------------------------------------------------------------------
1 | # See https://github.com/typesafegithub/github-actions-typing
2 | inputs:
3 | script:
4 | type: string
5 | github-token:
6 | type: string
7 | debug:
8 | type: boolean
9 | user-agent:
10 | type: string
11 | previews:
12 | type: list
13 | separator: ','
14 | list-item:
15 | type: string
16 | result-encoding:
17 | type: enum
18 | allowed-values:
19 | - string
20 | - json
21 | # Please check those outputs's description and set a proper type. 'string' is just set by default
22 | outputs:
23 | result:
24 | type: string
25 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/Case.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.yaml
2 |
3 | import io.github.typesafegithub.workflows.internal.InternalGithubActionsApi
4 |
5 | @InternalGithubActionsApi
6 | public inline fun > T.toSnakeCase(): String = snakeCaseOf(name)
7 |
8 | @InternalGithubActionsApi
9 | public fun snakeCaseOf(name: String): String {
10 | require(name.first() !in 'a'..'z' && name.all { it in 'a'..'z' || it in 'A'..'Z' })
11 | return pascalCaseRegex.findAll(name).joinToString(separator = "_") { it.value.lowercase() }
12 | }
13 |
14 | internal val pascalCaseRegex = Regex("[A-Z][a-z]*")
15 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/JavaBytecodeVersionTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows
2 |
3 | import io.github.typesafegithub.workflows.domain.Workflow
4 | import io.github.typesafegithub.workflows.testutils.JdkVersionToBytecodeVersion.JDK_11
5 | import io.github.typesafegithub.workflows.testutils.shouldHaveBytecodeVersion
6 | import io.kotest.core.spec.style.FunSpec
7 |
8 | class JavaBytecodeVersionTest :
9 | FunSpec(
10 | {
11 | test("library is built with desired Java bytecode version") {
12 | Workflow::class shouldHaveBytecodeVersion JDK_11
13 | }
14 | },
15 | )
16 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/Concurrency.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * Using concurrency allows running single workflow/job at a time
8 | * See https://docs.github.com/en/actions/using-jobs/using-concurrency
9 | * See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idconcurrency
10 | */
11 | @Serializable
12 | public data class Concurrency(
13 | val group: String,
14 | @SerialName("cancel-in-progress")
15 | val cancelInProgress: Boolean = false,
16 | )
17 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/AbstractResult.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain
2 |
3 | public abstract class AbstractResult internal constructor(
4 | private val value: String,
5 | ) {
6 | public infix fun eq(status: Status): String = "$value == $status"
7 |
8 | public infix fun neq(status: Status): String = "$value != $status"
9 |
10 | override fun toString(): String = value
11 |
12 | public enum class Status {
13 | Success,
14 | Failure,
15 | Cancelled,
16 | Skipped,
17 | ;
18 |
19 | override fun toString(): String = "'${name.lowercase()}'"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/domain/RunnerTypeTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain
2 |
3 | import io.kotest.assertions.throwables.shouldThrow
4 | import io.kotest.core.spec.style.FunSpec
5 |
6 | class RunnerTypeTest :
7 | FunSpec({
8 | context("Labelled") {
9 | test("should throw on invalid arguments") {
10 | shouldThrow {
11 | RunnerType.Labelled()
12 | }
13 | shouldThrow {
14 | RunnerType.Labelled(emptySet())
15 | }
16 | }
17 | }
18 | })
19 |
--------------------------------------------------------------------------------
/.github/workflows/setup-java.main.kts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env kotlin
2 | @file:Repository("https://repo.maven.apache.org/maven2/")
3 | @file:DependsOn("io.github.typesafegithub:github-workflows-kt:3.7.0")
4 |
5 | @file:Repository("https://bindings.krzeminski.it")
6 | @file:DependsOn("actions:setup-java:v5")
7 |
8 | import io.github.typesafegithub.workflows.actions.actions.SetupJava
9 | import io.github.typesafegithub.workflows.dsl.JobBuilder
10 |
11 | fun JobBuilder<*>.setupJava() =
12 | uses(
13 | name = "Set up JDK",
14 | action = SetupJava(
15 | javaVersion = "22",
16 | distribution = SetupJava.Distribution.Zulu,
17 | cache = SetupJava.BuildPlatform.Gradle,
18 | )
19 | )
20 |
--------------------------------------------------------------------------------
/code-generator/src/main/kotlin/io/github/typesafegithub/workflows/codegenerator/KspGeneratorProvider.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.codegenerator
2 |
3 | import com.google.devtools.ksp.processing.SymbolProcessor
4 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
5 | import com.google.devtools.ksp.processing.SymbolProcessorProvider
6 |
7 | class KspGeneratorProvider : SymbolProcessorProvider {
8 | override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor =
9 | KspGenerator(
10 | codeGenerator = environment.codeGenerator,
11 | libraryVersion = environment.options["library-version"] ?: error("No library version specified"),
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/docs/user-guide/job-outputs.md:
--------------------------------------------------------------------------------
1 | # Job outputs
2 |
3 | It's possible to pass output from a job in a somewhat type-safe way (that is: types aren't checked, but the field names
4 | are).
5 |
6 | First, define `outputs` parameter in `job` function, inheriting from `JobOutputs`:
7 |
8 | ```kotlin
9 | --8<-- "JobOutputsSnippets.kt:define-job-outputs-1"
10 | --8<-- "JobOutputsSnippets.kt:define-job-outputs-2"
11 | ```
12 |
13 | To set an output from within the job, use `jobOutputs`, and then an appropriate object field:
14 |
15 | ```kotlin
16 | --8<-- "JobOutputsSnippets.kt:set-job-outputs"
17 | ```
18 |
19 | and then use job's output from another job this way:
20 |
21 | ```kotlin
22 | --8<-- "JobOutputsSnippets.kt:use-job-outputs"
23 | ```
24 |
--------------------------------------------------------------------------------
/.github/workflows/test-gradle-project-using-bindings-server/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import java.net.URI
2 |
3 | plugins {
4 | kotlin("jvm") version "2.2.21"
5 | }
6 |
7 | repositories {
8 | mavenCentral()
9 | maven {
10 | url = URI("http://localhost:8080/")
11 | isAllowInsecureProtocol = true
12 | }
13 | }
14 |
15 | dependencies {
16 | // Regular, top-level action.
17 | implementation("actions:checkout:v4")
18 |
19 | // Nested action.
20 | implementation("gradle:actions__setup-gradle:v3")
21 |
22 | // Using specific version.
23 | implementation("actions:cache:v3.3.3")
24 |
25 | // Always untyped action.
26 | implementation("typesafegithub:always-untyped-action-for-tests:v1")
27 | }
28 |
--------------------------------------------------------------------------------
/code-generator/src/main/kotlin/io/github/typesafegithub/workflows/dsl/expressions/TextUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.dsl.expressions
2 |
3 | import java.util.Locale
4 |
5 | internal fun String.toPascalCase(): String {
6 | val hasOnlyUppercases = none { it in 'a'..'z' }
7 | val normalizedString = if (hasOnlyUppercases) lowercase() else this
8 | return normalizedString
9 | .replace("+", "-plus-")
10 | .split("-", "_", " ", ".", "/")
11 | .joinToString("") {
12 | it.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
13 | }
14 | }
15 |
16 | internal fun String.toCamelCase(): String = toPascalCase().replaceFirstChar { it.lowercase() }
17 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Something isn't working properly
4 | title: "[Bug] "
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | # Component
11 |
12 | * [ ] github-workflows-kt (library with the DSL)
13 | * [ ] bindings server (https://bindings.krzeminski.it)
14 | * [ ] I don't know
15 |
16 | # Action
17 |
18 | _Describe what you are trying to do._
19 |
20 | # Expected
21 |
22 | _What you would expect?_
23 |
24 | # Actual
25 |
26 | _What happens instead?_
27 |
28 | # Workaround, if exists
29 |
30 | _Manual adjustment of output YAML is not a workaround. Adjusting Kotlin code in a certain way is._
31 |
32 | # Version
33 |
34 | _If it's a bug in the library, specify it's version._
35 |
--------------------------------------------------------------------------------
/.github/workflows/_shared.main.kts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env kotlin
2 | @file:DependsOn("io.github.typesafegithub:github-workflows-kt:3.7.0")
3 |
4 | import io.github.typesafegithub.workflows.dsl.expressions.expr
5 |
6 | val disableScheduledJobInForks =
7 | expr { "${github.repository_owner} == 'typesafegithub' || ${github.event_name} != 'schedule'" }
8 |
9 | // The order is deliberate here. First, libraries with no dependencies are published.
10 | // Then, libraries that depend on already published libraries, and so on. Thanks to
11 | // such order, newly released artifacts already have their dependencies in place and
12 | // are ready to be used.
13 | val libraries = listOf(
14 | ":shared-internal",
15 | ":github-workflows-kt",
16 | ":action-binding-generator",
17 | ":action-updates-checker",
18 | )
19 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/dsl/expressions/Expression.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.dsl.expressions
2 |
3 | /**
4 | * Creates an expression, i.e. something evaluated by GitHub, from a string.
5 | *
6 | * https://docs.github.com/en/actions/learn-github-actions/expressions#about-expressions
7 | */
8 | public fun expr(value: String): String = "\${{ ${value.removePrefix("$")} }}"
9 |
10 | /**
11 | * Creates an expression, i.e. something evaluated by GitHub, using type-safe API.
12 | *
13 | * https://docs.github.com/en/actions/learn-github-actions/expressions#about-expressions
14 | * https://docs.github.com/en/actions/learn-github-actions/contexts
15 | */
16 | public fun expr(expression: Contexts.() -> String): String = with(Contexts) { expr(expression()) }
17 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/yaml/ContextMapping.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.yaml
2 |
3 | import io.github.typesafegithub.workflows.domain.contexts.Contexts
4 | import io.github.typesafegithub.workflows.domain.contexts.GithubContext
5 | import kotlinx.serialization.json.Json
6 |
7 | internal fun loadContextsFromEnvVars(getenv: (String) -> String?): Contexts {
8 | fun getEnvVarOrFail(varName: String): String = getenv(varName) ?: error("$varName should be set!")
9 |
10 | val githubContextRaw = getEnvVarOrFail("GHWKT_GITHUB_CONTEXT_JSON")
11 | val githubContext = json.decodeFromString(githubContextRaw)
12 | return Contexts(
13 | github = githubContext,
14 | )
15 | }
16 |
17 | private val json = Json { ignoreUnknownKeys = true }
18 |
--------------------------------------------------------------------------------
/.github/workflows/test-gradle-project-using-bindings-server/src/main/kotlin/Main.kt:
--------------------------------------------------------------------------------
1 | import io.github.typesafegithub.workflows.actions.actions.Cache
2 | import io.github.typesafegithub.workflows.actions.actions.Checkout
3 | import io.github.typesafegithub.workflows.actions.actions.Checkout_Untyped
4 | import io.github.typesafegithub.workflows.actions.gradle.ActionsSetupGradle
5 | import io.github.typesafegithub.workflows.actions.typesafegithub.AlwaysUntypedActionForTests_Untyped
6 |
7 | fun main() {
8 | println(Checkout_Untyped(fetchTags_Untyped = "false"))
9 | println(Checkout(fetchTags = false))
10 | println(Checkout(fetchTags_Untyped = "false"))
11 | println(AlwaysUntypedActionForTests_Untyped(foobar_Untyped = "baz"))
12 | println(ActionsSetupGradle())
13 | println(Cache(path = listOf("some-path"), key = "some-key"))
14 | }
15 |
--------------------------------------------------------------------------------
/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.actionbindinggenerator.domain
2 |
3 | /**
4 | * The version part that is significant when generating the YAML output.
5 | * This is used to enable usage of Maven ranges without needing to specify a custom version
6 | * each time instantiating an action.
7 | */
8 | public enum class SignificantVersion {
9 | /**
10 | * Only write the major version to the generated YAML.
11 | */
12 | MAJOR,
13 |
14 | /**
15 | * Only write the major and minor version to the generated YAML.
16 | */
17 | MINOR,
18 |
19 | /**
20 | * Write the full version to the generated YAML.
21 | */
22 | FULL,
23 | ;
24 |
25 | override fun toString(): String = super.toString().lowercase()
26 | }
27 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/dsl/expressions/contexts/RunnerContext.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.dsl.expressions.contexts
2 |
3 | import io.github.typesafegithub.workflows.dsl.expressions.ExpressionContext
4 |
5 | /**
6 | * The runner context contains information about the runner that is executing the current job.
7 | * https://docs.github.com/en/actions/learn-github-actions/contexts#example-contents-of-the-runner-context
8 | */
9 | public object RunnerContext : ExpressionContext("runner") {
10 | public val name: String by propertyToExprPath
11 | public val os: String by propertyToExprPath
12 | public val arch: String by propertyToExprPath
13 | public val temp: String by propertyToExprPath
14 | public val tool_cache: String by propertyToExprPath
15 | public val workspace: String by propertyToExprPath
16 | }
17 |
--------------------------------------------------------------------------------
/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithOutputsTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.actionbindinggenerator.bindingsfromunittests
2 |
3 | import io.github.typesafegithub.workflows.actions.johnsmith.ActionWithOutputs
4 | import io.kotest.core.spec.style.DescribeSpec
5 | import io.kotest.matchers.shouldBe
6 |
7 | class ActionWithOutputsTest : DescribeSpec({
8 | it("fields have correct output placeholders") {
9 | // given
10 | val outputs = ActionWithOutputs(fooBar = "1").buildOutputObject("someStepId")
11 |
12 | // when & then
13 | outputs.bazGoo shouldBe "steps.someStepId.outputs.baz-goo"
14 | outputs.looWoz shouldBe "steps.someStepId.outputs.loo-woz"
15 | outputs["custom-output"] shouldBe "steps.someStepId.outputs.custom-output"
16 | }
17 | })
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/core-feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Core feature request
3 | about: Request adding support for some core GitHub Actions workflows feature
4 | title: "[Core feature request] "
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **What feature do you need?**
11 | _Please add a link to GitHub docs (https://docs.github.com/en/actions/using-workflows/) which describes the desired feature._
12 |
13 | **Do you have an example usage?**
14 | _Best if a YAML snippet is pasted here, or if your project is open-source, an URL to your workflow._
15 |
16 | **Is there a workaround for not having this feature? If yes, please describe it.**
17 | _Please take a look at https://krzema12.github.io/github-workflows-kt/user-guide/compensating-librarys-missing-features/ as a mechanism designed to work around most missing features, and let us know here if it works for you short-term._
18 |
--------------------------------------------------------------------------------
/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithSomeOptionalInputsTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.actionbindinggenerator.bindingsfromunittests
2 |
3 | import io.github.typesafegithub.workflows.actions.johnsmith.ActionWithSomeOptionalInputs
4 | import io.kotest.core.spec.style.DescribeSpec
5 | import io.kotest.matchers.shouldBe
6 |
7 | class ActionWithSomeOptionalInputsTest : DescribeSpec({
8 | it("renders with defaults") {
9 | // given
10 | val action = ActionWithSomeOptionalInputs(
11 | bazGoo = "def456",
12 | `package` = "qwe789"
13 | )
14 |
15 | // when
16 | val yaml = action.toYamlArguments()
17 |
18 | // then
19 | yaml shouldBe linkedMapOf(
20 | "baz-goo" to "def456",
21 | "package" to "qwe789",
22 | )
23 | }
24 | })
25 |
--------------------------------------------------------------------------------
/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/SimpleActionWithRequiredStringInputsTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.actionbindinggenerator.bindingsfromunittests
2 |
3 | import io.github.typesafegithub.workflows.actions.johnsmith.SimpleActionWithRequiredStringInputs
4 | import io.kotest.core.spec.style.DescribeSpec
5 | import io.kotest.matchers.shouldBe
6 |
7 | class SimpleActionWithRequiredStringInputsTest : DescribeSpec({
8 | it("renders with defaults") {
9 | // given
10 | val action = SimpleActionWithRequiredStringInputs(
11 | fooBar = "abc123",
12 | bazGoo = "def456",
13 | )
14 |
15 | // when
16 | val yaml = action.toYamlArguments()
17 |
18 | // then
19 | yaml shouldBe linkedMapOf(
20 | "foo-bar" to "abc123",
21 | "baz-goo" to "def456",
22 | )
23 | }
24 | })
25 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/dsl/expressions/ExpressionContext.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.dsl.expressions
2 |
3 | @Suppress("ConstructorParameterNaming")
4 | public open class ExpressionContext(
5 | internal val _path: String,
6 | public val propertyToExprPath: Map = MapFromLambda { propertyName -> "$_path.$propertyName" },
7 | ) : Map by propertyToExprPath
8 |
9 | internal class MapFromLambda(
10 | val operation: (String) -> T,
11 | ) : Map by emptyMap() {
12 | override fun containsKey(key: String) = true
13 |
14 | override fun get(key: String): T = operation(key)
15 |
16 | override fun getOrDefault(
17 | key: String,
18 | defaultValue: T,
19 | ): T = get(key)
20 | }
21 |
22 | internal class FakeList(
23 | val name: String,
24 | ) : List by emptyList() {
25 | override fun get(index: Int): String = "$name[$index]"
26 | }
27 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/dsl/expressions/contexts/SecretsContext.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.dsl.expressions.contexts
2 |
3 | import io.github.typesafegithub.workflows.dsl.expressions.ExpressionContext
4 |
5 | /**
6 | * Encrypted secrets
7 | *
8 | * Encrypted secrets allow you to store sensitive information in your organization,
9 | * repository, or repository environments.
10 | *
11 | * https://docs.github.com/en/actions/security-guides/encrypted-secrets
12 | */
13 | public object SecretsContext : ExpressionContext("secrets") {
14 | /***
15 | * GITHUB_TOKEN is a secret that is automatically created for every workflow run,
16 | * and is always included in the secrets context. For more information, see "Automatic token authentication."
17 | * https://docs.github.com/en/actions/security-guides/automatic-token-authentication
18 | */
19 | public val GITHUB_TOKEN: String by propertyToExprPath
20 | }
21 |
--------------------------------------------------------------------------------
/maven-binding-builder/src/test/kotlin/io/github/typesafegithub/workflows/mavenbinding/JavaBytecodeVersionTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.mavenbinding
2 |
3 | import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords
4 | import io.github.typesafegithub.workflows.testutils.JdkVersionToBytecodeVersion.JDK_8
5 | import io.github.typesafegithub.workflows.testutils.shouldHaveBytecodeVersion
6 | import io.kotest.core.spec.style.FunSpec
7 | import io.ktor.client.HttpClient
8 | import io.ktor.client.engine.cio.CIO
9 |
10 | class JavaBytecodeVersionTest :
11 | FunSpec(
12 | {
13 | test("bindings are built with desired Java bytecode version") {
14 | val actionCoords = ActionCoords("actions", "setup-java", "v3")
15 | val jars = actionCoords.buildJars(httpClient = HttpClient(CIO))!!
16 | jars.randomClassFile() shouldHaveBytecodeVersion JDK_8
17 | }
18 | },
19 | )
20 |
--------------------------------------------------------------------------------
/docs/user-guide/nightly-builds.md:
--------------------------------------------------------------------------------
1 | # Nightly builds
2 |
3 | Sometimes you may want to test a change that has been already merged to `main`, but not yet officially released. In this
4 | case, you can use snapshots published for each commit to the `main` branch.
5 |
6 | To use a given "snapshot" version, e.g. `1.3.2-SNAPSHOT`, replace your scripts' preamble with:
7 |
8 | ```kotlin
9 | @file:Repository("https://central.sonatype.com/repository/maven-snapshots/")
10 | @file:DependsOn("io.github.typesafegithub:github-workflows-kt:1.3.2-SNAPSHOT")
11 | ```
12 |
13 | Remember that requesting version `1.3.2-SNAPSHOT`, if it's being actively developed, may return a different build of the
14 | library each time it's requested. It can also happen occasionally that the snapshot doesn't correspond to any commit on
15 | the `main` branch, and instead some PR that wasn't yet merged. That's why the snapshots are meant to quickly check
16 | something simple that wasn't yet released, not for depending on such unstable version constantly.
17 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "github-workflows-kt-monorepo"
2 |
3 | apply(from = "./buildSrc/repositories.settings.gradle.kts")
4 |
5 | include(
6 | "github-workflows-kt",
7 | "action-binding-generator",
8 | "action-updates-checker",
9 | "maven-binding-builder",
10 | "jit-binding-server",
11 | "shared-internal",
12 | "code-generator",
13 | "test-utils",
14 | )
15 |
16 | plugins {
17 | id("com.gradle.develocity") version "4.3"
18 | }
19 |
20 | dependencyResolutionManagement {
21 | @Suppress("UnstableApiUsage") // Central declaration of repositories is an incubating feature
22 | repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
23 | }
24 |
25 | develocity {
26 | buildScan {
27 | termsOfUseUrl = "https://gradle.com/terms-of-service"
28 | termsOfUseAgree = "yes"
29 | publishing.onlyIf {
30 | System.getenv("GITHUB_ACTIONS") == "true" && it.buildResult.failures.isNotEmpty()
31 | }
32 | }
33 | }
34 |
35 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
36 |
--------------------------------------------------------------------------------
/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/Plugins.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.jitbindingserver
2 |
3 | import io.ktor.http.HttpHeaders
4 | import io.ktor.server.application.Application
5 | import io.ktor.server.application.install
6 | import io.ktor.server.metrics.micrometer.MicrometerMetrics
7 | import io.ktor.server.plugins.callid.CallId
8 | import io.ktor.server.plugins.callid.callIdMdc
9 | import io.ktor.server.plugins.callid.generate
10 | import io.ktor.server.plugins.calllogging.CallLogging
11 | import io.micrometer.prometheusmetrics.PrometheusMeterRegistry
12 |
13 | fun Application.installPlugins(prometheusRegistry: PrometheusMeterRegistry) {
14 | install(CallId) {
15 | generate(length = 15, dictionary = "abcdefghijklmnopqrstuvwxyz0123456789")
16 | replyToHeader(HttpHeaders.XRequestId)
17 | }
18 |
19 | install(CallLogging) {
20 | callIdMdc("request-id")
21 | }
22 |
23 | install(MicrometerMetrics) {
24 | registry = prometheusRegistry
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/code-generator/src/main/kotlin/io/github/typesafegithub/workflows/codegenerator/KspGenerator.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.codegenerator
2 |
3 | import com.google.devtools.ksp.processing.CodeGenerator
4 | import com.google.devtools.ksp.processing.Resolver
5 | import com.google.devtools.ksp.processing.SymbolProcessor
6 | import com.google.devtools.ksp.symbol.KSAnnotated
7 | import io.github.typesafegithub.workflows.dsl.expressions.generateEventPayloads
8 | import io.github.typesafegithub.workflows.generateVersionInfo
9 |
10 | class KspGenerator(
11 | private val codeGenerator: CodeGenerator,
12 | private val libraryVersion: String,
13 | ) : SymbolProcessor {
14 | private var wasRun = false
15 |
16 | override fun process(resolver: Resolver): List {
17 | if (!wasRun) {
18 | generateEventPayloads(codeGenerator = codeGenerator)
19 | generateVersionInfo(codeGenerator = codeGenerator, libraryVersion = libraryVersion)
20 | wasRun = true
21 | }
22 | return emptyList()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/Checksums.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.mavenbinding
2 |
3 | import java.security.MessageDigest
4 |
5 | internal fun ByteArray.md5Checksum() = checksum("MD5")
6 |
7 | internal fun String.md5Checksum() = checksum("MD5")
8 |
9 | internal fun ByteArray.sha1Checksum() = checksum("SHA-1")
10 |
11 | internal fun String.sha1Checksum() = checksum("SHA-1")
12 |
13 | internal fun ByteArray.sha256Checksum() = checksum("SHA-256")
14 |
15 | internal fun String.sha256Checksum() = checksum("SHA-256")
16 |
17 | internal fun ByteArray.sha512Checksum() = checksum("SHA-512")
18 |
19 | internal fun String.sha512Checksum() = checksum("SHA-512")
20 |
21 | private fun ByteArray.checksum(algorithm: String): String {
22 | val digest = MessageDigest.getInstance(algorithm)
23 | val hashBytes = digest.digest(this)
24 | return hashBytes.joinToString("") { "%02x".format(it) }
25 | }
26 |
27 | private fun String.checksum(algorithm: String) = toByteArray(charset = Charsets.UTF_8).checksum(algorithm)
28 |
--------------------------------------------------------------------------------
/code-generator/src/main/kotlin/io/github/typesafegithub/workflows/VersionInfo.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows
2 |
3 | import com.google.devtools.ksp.processing.CodeGenerator
4 | import com.squareup.kotlinpoet.CodeBlock
5 | import com.squareup.kotlinpoet.FileSpec
6 | import com.squareup.kotlinpoet.FunSpec
7 | import com.squareup.kotlinpoet.KModifier
8 | import com.squareup.kotlinpoet.ksp.writeTo
9 |
10 | fun generateVersionInfo(
11 | codeGenerator: CodeGenerator,
12 | libraryVersion: String,
13 | ) {
14 | val funSpec =
15 | FunSpec
16 | .builder(name = "getLibraryVersion")
17 | .addModifiers(KModifier.INTERNAL)
18 | .returns(String::class)
19 | .addCode(CodeBlock.of("return \"$libraryVersion\""))
20 | .build()
21 |
22 | val fileSpec =
23 | FileSpec
24 | .builder(packageName = "io.github.typesafegithub.workflows.internal", fileName = "VersionInfo.kt")
25 | .addFunction(funSpec)
26 | .build()
27 |
28 | fileSpec.writeTo(codeGenerator = codeGenerator, aggregating = false)
29 | }
30 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/actions/CustomActionTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.actions
2 |
3 | import io.github.typesafegithub.workflows.domain.actions.CustomAction
4 | import io.kotest.core.spec.style.FunSpec
5 | import io.kotest.matchers.shouldBe
6 |
7 | class CustomActionTest :
8 | FunSpec({
9 |
10 | test("custom action") {
11 | // given
12 | val customAction =
13 | CustomAction(
14 | actionOwner = "xu-cheng",
15 | actionName = "latex-action",
16 | actionVersion = "v2",
17 | inputs =
18 | mapOf(
19 | "root_file" to "report.tex",
20 | "compiler" to "latexmk",
21 | ),
22 | )
23 |
24 | // given
25 | val outputs = customAction.buildOutputObject("someStepId")
26 |
27 | // when & then
28 | outputs["custom-output"] shouldBe "steps.someStepId.outputs.custom-output"
29 | }
30 | })
31 |
--------------------------------------------------------------------------------
/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/utils/TextUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.actionbindinggenerator.utils
2 |
3 | import java.util.Locale
4 |
5 | private val normalizationSplitRegex = """[^\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}&&[^_]]++""".toRegex()
6 |
7 | internal fun String.toPascalCase(): String {
8 | val hasOnlyUppercases = none { it in 'a'..'z' }
9 | val normalizedString = if (hasOnlyUppercases) lowercase() else this
10 | return normalizedString
11 | .replace("+", "-plus-")
12 | .split(normalizationSplitRegex)
13 | .joinToString("") {
14 | it.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
15 | }
16 | }
17 |
18 | internal fun String.toCamelCase(): String = toPascalCase().replaceFirstChar { it.lowercase() }
19 |
20 | internal fun String.toKotlinPackageName(): String =
21 | replace("-", "")
22 | .lowercase()
23 |
24 | internal fun String.removeTrailingWhitespacesForEachLine() = lines().joinToString(separator = "\n") { it.trimEnd() }
25 |
--------------------------------------------------------------------------------
/action-binding-generator/src/test/resources/types/action.yml:
--------------------------------------------------------------------------------
1 | name: GitHub Script
2 | author: GitHub
3 | description: Run simple scripts using the GitHub client
4 | branding:
5 | color: blue
6 | icon: code
7 | inputs:
8 | script:
9 | description: The script to run
10 | required: true
11 | github-token:
12 | description: The GitHub token used to create an authenticated client
13 | default: ${{ github.token }}
14 | required: false
15 | debug:
16 | description: Whether to tell the GitHub client to log details of its requests
17 | default: "false"
18 | required: false
19 | user-agent:
20 | description: An optional user-agent string
21 | default: actions/github-script
22 | required: false
23 | previews:
24 | description: A comma-separated list of API previews to accept
25 | required: false
26 | result-encoding:
27 | description: Either "string" or "json" (default "json")—how the result will be encoded
28 | default: json
29 | required: false
30 | outputs:
31 | result:
32 | description: The return value of the script, stringified with `JSON.stringify`
33 | runs:
34 | using: node16
35 | main: dist/index.js
36 |
--------------------------------------------------------------------------------
/shared-internal/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | buildsrc.convention.`kotlin-jvm`
3 | buildsrc.convention.publishing
4 |
5 | kotlin("plugin.serialization")
6 | id("io.gitlab.arturbosch.detekt")
7 | }
8 |
9 | group = rootProject.group
10 | version = rootProject.version
11 |
12 | dependencies {
13 | api("io.arrow-kt:arrow-core:2.2.0")
14 | // we cannot use a BOM due to limitation in kotlin scripting when resolving the transitive KMM variant dependencies
15 | // note: see https://youtrack.jetbrains.com/issue/KT-67618
16 | api("io.ktor:ktor-client-core:3.3.3")
17 | api("io.micrometer:micrometer-core:1.15.5")
18 | implementation("io.ktor:ktor-client-cio:3.3.3")
19 | implementation("io.ktor:ktor-client-content-negotiation:3.3.3")
20 | implementation("io.ktor:ktor-client-logging:3.3.3")
21 | implementation("io.ktor:ktor-serialization-kotlinx-json:3.3.3")
22 | implementation("io.github.oshai:kotlin-logging:7.0.13")
23 | implementation("com.auth0:java-jwt:4.5.0")
24 | implementation("org.kohsuke:github-api:1.330")
25 |
26 | testImplementation("io.kotest:kotest-extensions-mockserver:6.0.7")
27 | testImplementation("org.slf4j:slf4j-simple:2.0.17")
28 | }
29 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing guidelines
2 |
3 | Thanks for finding a moment for your contribution! We really appreciate it!
4 | The below rules are here to speed things up and streamline work on both sides - us as library maintainers and you as the contributor.
5 |
6 | ## Reporting a bug
7 |
8 | First check if it's not already reported. Use the "bug" tag for filtering.
9 | If it's not, create a new one using the template. If it already exists, give it a thumb-up and optionally share any helpful details in the comments.
10 |
11 | ## Requesting a feature
12 |
13 | There's also a designated template for it, and the rule of checking if the request is already tracked also applies.
14 |
15 | ## Contributing a feature
16 |
17 | Each feature contribution must have an accompanying feature request issue, best if we discuss the approach in the issue first to avoid misunderstandings and wasted time. If the feature is simple, go ahead with creating the issue and the PR in parallel.
18 |
19 | If you're creating a new feature branch in this repository (doesn't count if it's your fork), it has to start with issue number, e.g. `123-new-steps-API`, and the issue has to be open. Otherwise, the branch is assumed to be safe to delete.
20 |
--------------------------------------------------------------------------------
/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/PackageArtifactsBuilding.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.mavenbinding
2 |
3 | import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords
4 | import io.micrometer.core.instrument.MeterRegistry
5 |
6 | suspend fun buildPackageArtifacts(
7 | actionCoords: ActionCoords,
8 | githubAuthToken: String,
9 | prefetchBindingArtifacts: (Collection) -> Unit,
10 | meterRegistry: MeterRegistry,
11 | ): Map {
12 | val mavenMetadata =
13 | actionCoords.buildMavenMetadataFile(
14 | githubAuthToken = githubAuthToken,
15 | prefetchBindingArtifacts = prefetchBindingArtifacts,
16 | meterRegistry = meterRegistry,
17 | ) ?: return emptyMap()
18 | return mapOf(
19 | "maven-metadata.xml" to mavenMetadata,
20 | "maven-metadata.xml.md5" to mavenMetadata.md5Checksum(),
21 | "maven-metadata.xml.sha1" to mavenMetadata.sha1Checksum(),
22 | "maven-metadata.xml.sha256" to mavenMetadata.sha256Checksum(),
23 | "maven-metadata.xml.sha512" to mavenMetadata.sha512Checksum(),
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/dsl/expressions/Contexts.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.dsl.expressions
2 |
3 | import io.github.typesafegithub.workflows.dsl.expressions.contexts.EnvContext
4 | import io.github.typesafegithub.workflows.dsl.expressions.contexts.FunctionsContext
5 | import io.github.typesafegithub.workflows.dsl.expressions.contexts.GitHubContext
6 | import io.github.typesafegithub.workflows.dsl.expressions.contexts.RunnerContext
7 | import io.github.typesafegithub.workflows.dsl.expressions.contexts.SecretsContext
8 | import io.github.typesafegithub.workflows.dsl.expressions.contexts.VarsContext
9 |
10 | /**
11 | * Root elements of GitHub expressions.
12 | *
13 | * https://docs.github.com/en/actions/learn-github-actions/expressions#about-expressions
14 | * https://docs.github.com/en/actions/learn-github-actions/contexts
15 | */
16 | public object Contexts : FunctionsContext() {
17 | public val env: EnvContext = EnvContext
18 | public val github: GitHubContext = GitHubContext
19 | public val runner: RunnerContext = RunnerContext
20 | public val secrets: SecretsContext = SecretsContext
21 | public val vars: VarsContext = VarsContext
22 | }
23 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/Push.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.SerialName
5 |
6 | @kotlinx.serialization.Serializable
7 | public data class Push(
8 | val branches: List? = null,
9 | @SerialName("branches-ignore")
10 | val branchesIgnore: List? = null,
11 | val tags: List? = null,
12 | @SerialName("tags-ignore")
13 | val tagsIgnore: List? = null,
14 | val paths: List? = null,
15 | @SerialName("paths-ignore")
16 | val pathsIgnore: List? = null,
17 | override val _customArguments: Map = mapOf(),
18 | ) : Trigger() {
19 | init {
20 | require(!(branches != null && branchesIgnore != null)) {
21 | "Cannot define both 'branches' and 'branchesIgnore'!"
22 | }
23 | require(!(tags != null && tagsIgnore != null)) {
24 | "Cannot define both 'tags' and 'tagsIgnore'!"
25 | }
26 | require(!(paths != null && pathsIgnore != null)) {
27 | "Cannot define both 'paths' and 'pathsIgnore'!"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/test-utils/src/main/kotlin/io/github/typesafegithub/workflows/testutils/JavaBytecodeUtils.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.testutils
2 |
3 | import io.kotest.matchers.shouldBe
4 | import java.io.ByteArrayInputStream
5 | import java.io.DataInputStream
6 | import java.io.InputStream
7 | import kotlin.reflect.KClass
8 |
9 | infix fun KClass<*>.shouldHaveBytecodeVersion(expectedVersion: String) {
10 | this::class.java.classLoader
11 | .getResourceAsStream(
12 | this.qualifiedName!!.replace(".", "/") + ".class",
13 | )!!
14 | .use { inputStream ->
15 | inputStream shouldHaveBytecodeVersion expectedVersion
16 | }
17 | }
18 |
19 | infix fun ByteArray.shouldHaveBytecodeVersion(expectedVersion: String) =
20 | ByteArrayInputStream(this) shouldHaveBytecodeVersion expectedVersion
21 |
22 | private infix fun InputStream.shouldHaveBytecodeVersion(expectedVersion: String) {
23 | val dataInputStream = DataInputStream(this)
24 | require(dataInputStream.readInt() == 0xCAFEBABE.toInt()) { "Invalid class header" }
25 | val minor = dataInputStream.readUnsignedShort()
26 | val major = dataInputStream.readUnsignedShort()
27 |
28 | val actualVersion = "$major.$minor"
29 | actualVersion shouldBe expectedVersion
30 | }
31 |
--------------------------------------------------------------------------------
/action-binding-generator/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jmailen.gradle.kotlinter.tasks.ConfigurableKtLintTask
2 | import org.jmailen.gradle.kotlinter.tasks.FormatTask
3 | import org.jmailen.gradle.kotlinter.tasks.LintTask
4 |
5 | plugins {
6 | buildsrc.convention.`kotlin-jvm`
7 | buildsrc.convention.publishing
8 | kotlin("plugin.serialization")
9 |
10 | id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.18.1"
11 | }
12 |
13 | group = rootProject.group
14 | version = rootProject.version
15 |
16 | dependencies {
17 | implementation("com.squareup:kotlinpoet:2.2.0")
18 | implementation("com.charleskorn.kaml:kaml:0.104.0")
19 | implementation("io.ktor:ktor-client-core:3.3.3")
20 | implementation("io.ktor:ktor-client-cio:3.3.3")
21 | implementation("io.github.oshai:kotlin-logging:7.0.13")
22 | implementation(projects.sharedInternal)
23 |
24 | testImplementation("io.ktor:ktor-client-mock:3.3.3")
25 | testImplementation(projects.githubWorkflowsKt)
26 | }
27 |
28 | kotlin {
29 | explicitApi()
30 | }
31 |
32 | fun ConfigurableKtLintTask.kotlinterConfig() {
33 | exclude("**/bindingsfromunittests/**")
34 | }
35 |
36 | tasks.withType {
37 | kotlinterConfig()
38 | }
39 |
40 | tasks.withType {
41 | kotlinterConfig()
42 | }
43 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/WorkflowDispatch.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | // https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_dispatchinputs
8 | @Serializable
9 | public data class WorkflowDispatch(
10 | val inputs: Map = emptyMap(),
11 | override val _customArguments: Map = mapOf(),
12 | ) : Trigger() {
13 | @Serializable
14 | public enum class Type {
15 | @SerialName("choice")
16 | Choice,
17 |
18 | @SerialName("environment")
19 | Environment,
20 |
21 | @SerialName("boolean")
22 | Boolean,
23 |
24 | @SerialName("number")
25 | Number,
26 |
27 | @SerialName("string")
28 | String,
29 | }
30 |
31 | @Serializable
32 | public class Input(
33 | public val description: String,
34 | public val required: Boolean,
35 | public val type: Type,
36 | public val options: List = emptyList(),
37 | public val default: String? = null,
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/ClassNamingTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.actionbindinggenerator.generation
2 |
3 | import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords
4 | import io.github.typesafegithub.workflows.actionbindinggenerator.domain.SignificantVersion.FULL
5 | import io.github.typesafegithub.workflows.actionbindinggenerator.domain.SignificantVersion.MAJOR
6 | import io.kotest.core.spec.style.FunSpec
7 | import io.kotest.matchers.shouldBe
8 |
9 | class ClassNamingTest :
10 | FunSpec({
11 | context("buildActionClassName") {
12 | listOf(
13 | ActionCoords("irrelevant", "some-action-name", "v2") to "SomeActionName",
14 | ActionCoords("irrelevant", "some-action-name", "v2", FULL, "subaction") to "SomeActionNameSubaction",
15 | ActionCoords("irrelevant", "some-action-name", "v2", FULL, "foo/bar/baz") to "SomeActionNameFooBarBaz",
16 | ActionCoords("irrelevant", "some-action-name", "v2", MAJOR, "foo/bar/baz") to "SomeActionNameFooBarBaz",
17 | ).forEach { (input, output) ->
18 | test("should get '$input' and produce '$output'") {
19 | input.buildActionClassName() shouldBe output
20 | }
21 | }
22 | }
23 | })
24 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/dsl/Container.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.dsl
2 |
3 | import io.github.typesafegithub.workflows.domain.PortMapping
4 | import io.github.typesafegithub.workflows.domain.PortMapping.Protocol
5 | import io.github.typesafegithub.workflows.domain.VolumeMapping
6 |
7 | public fun port(
8 | mapping: Pair,
9 | protocol: Protocol = Protocol.All,
10 | ): PortMapping = PortMapping(host = mapping.first, container = mapping.second, protocol = protocol)
11 |
12 | public fun port(
13 | host: Int,
14 | protocol: Protocol = Protocol.All,
15 | ): PortMapping = PortMapping(host = host, protocol = protocol)
16 |
17 | public fun tcp(mapping: Pair): PortMapping = port(mapping, Protocol.TCP)
18 |
19 | public fun tcp(host: Int): PortMapping = port(host, Protocol.TCP)
20 |
21 | public fun udp(mapping: Pair): PortMapping = port(mapping, Protocol.UDP)
22 |
23 | public fun udp(host: Int): PortMapping = port(host, Protocol.UDP)
24 |
25 | public fun volume(
26 | mapping: Pair,
27 | isReadOnly: Boolean = false,
28 | ): VolumeMapping = VolumeMapping(source = mapping.first, target = mapping.second, isReadOnly = isReadOnly)
29 |
30 | public fun volume(
31 | target: String,
32 | isReadOnly: Boolean = false,
33 | ): VolumeMapping = VolumeMapping(target = target, isReadOnly = isReadOnly)
34 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/domain/triggers/PullRequestTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import io.kotest.assertions.throwables.shouldThrow
4 | import io.kotest.core.spec.style.FunSpec
5 | import io.kotest.matchers.shouldBe
6 |
7 | class PullRequestTest :
8 | FunSpec({
9 | context("validation errors") {
10 | test("both 'branches' and 'branchesIgnore' defined") {
11 | val exception =
12 | shouldThrow {
13 | PullRequest(
14 | branches = listOf("branch1"),
15 | branchesIgnore = listOf("branch2"),
16 | )
17 | }
18 | exception.message shouldBe "Cannot define both 'branches' and 'branchesIgnore'!"
19 | }
20 |
21 | test("both 'paths' and 'pathsIgnore' defined") {
22 | val exception =
23 | shouldThrow {
24 | PullRequest(
25 | paths = listOf("path1"),
26 | pathsIgnore = listOf("path2"),
27 | )
28 | }
29 | exception.message shouldBe "Cannot define both 'paths' and 'pathsIgnore'!"
30 | }
31 | }
32 | })
33 |
--------------------------------------------------------------------------------
/jit-binding-server/src/main/resources/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%-35.35t> <%x> <%X> <%50.50c> %m}{TRACE = magenta}%n]]>
8 |
9 |
10 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | <%-35.35t> <%x> <%X> <%50.50c> %m}{TRACE = magenta}%n]]>
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/domain/triggers/PullRequestTargetTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import io.kotest.assertions.throwables.shouldThrow
4 | import io.kotest.core.spec.style.FunSpec
5 | import io.kotest.matchers.shouldBe
6 |
7 | class PullRequestTargetTest :
8 | FunSpec({
9 | context("validation errors") {
10 | test("both 'branches' and 'branchesIgnore' defined") {
11 | val exception =
12 | shouldThrow {
13 | PullRequestTarget(
14 | branches = listOf("branch1"),
15 | branchesIgnore = listOf("branch2"),
16 | )
17 | }
18 | exception.message shouldBe "Cannot define both 'branches' and 'branchesIgnore'!"
19 | }
20 |
21 | test("both 'paths' and 'pathsIgnore' defined") {
22 | val exception =
23 | shouldThrow {
24 | PullRequestTarget(
25 | paths = listOf("path1"),
26 | pathsIgnore = listOf("path2"),
27 | )
28 | }
29 | exception.message shouldBe "Cannot define both 'paths' and 'pathsIgnore'!"
30 | }
31 | }
32 | })
33 |
--------------------------------------------------------------------------------
/MAINTENANCE.md:
--------------------------------------------------------------------------------
1 | This file describes various maintenance tasks, relevant for project maintainers only.
2 |
3 | # Release a new version
4 |
5 | It currently happens whenever necessary, there's no agreed cadence. Whenever we see there's an important bug fix or a feature to roll out, or an important dependency update, we release.
6 |
7 | 1. Remove `-SNAPSHOT` for version starting from [build.gradle.kts](https://github.com/typesafegithub/github-workflows-kt/blob/main/build.gradle.kts). By building the whole project with `./gradlew build`, you will learn what other places need to be adjusted. There's one place that needs extra care: in PomBuilding.kt, there's `LATEST_RELASED_LIBRARY_VERSION` - set it to the version you're going to deploy in a minute. Once done, create a commit using this pattern for commit message: `chore: prepare for releasing version `.
8 | 1. Once CI is green for the newly merged commits, create and push an annotated tag:
9 | ```
10 | COMMIT_TITLE=`git log -1 --pretty=%B`
11 | VERSION=${COMMIT_TITLE#"chore: prepare for releasing version "}
12 | git tag -a "v$VERSION" -m "chore: release version $VERSION" && git push origin "v$VERSION"
13 | ```
14 | 1. On `main` branch, change version to prepare for the next development cycle, e.g. if it was `1.2.3-SNAPSHOT` before and we released it as `1.2.3`, change the version to `1.2.4-SNAPSHOT` (a minimal increase, in the patch version number).
15 | 1. Ensure that the release job has succeeded.
16 |
--------------------------------------------------------------------------------
/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/Typing.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.actionbindinggenerator.typing
2 |
3 | public sealed interface Typing
4 |
5 | internal object BooleanTyping : Typing
6 |
7 | internal data class EnumTyping(
8 | /**
9 | * Custom type name when used in Kotlin code.
10 | * Null means that the type name should be inferred from the input name.
11 | */
12 | val typeName: String? = null,
13 | /**
14 | * Items as required by the action, e.g. foo-bar-1
15 | */
16 | val items: List,
17 | /**
18 | * Items in a more readable form when used in Kotlin code and following its conventions, e.g. FooBar1.
19 | * Null means that these names should be inferred from the items.
20 | */
21 | val itemsNames: List? = null,
22 | ) : Typing
23 |
24 | internal object FloatTyping : Typing
25 |
26 | internal object IntegerTyping : Typing
27 |
28 | internal data class IntegerWithSpecialValueTyping(
29 | /**
30 | * Custom type name when used in Kotlin code.
31 | * Null means that the type name should be inferred from the input name.
32 | */
33 | val typeName: String? = null,
34 | val specialValues: Map,
35 | ) : Typing
36 |
37 | internal data class ListOfTypings(
38 | val delimiter: String,
39 | val typing: Typing = StringTyping,
40 | ) : Typing
41 |
42 | internal object StringTyping : Typing
43 |
--------------------------------------------------------------------------------
/code-generator/src/test/kotlin/io/github/typesafegithub/workflows/dsl/expressions/TextUtilsTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.dsl.expressions
2 |
3 | import io.kotest.core.spec.style.FunSpec
4 | import io.kotest.matchers.shouldBe
5 |
6 | class TextUtilsTest :
7 | FunSpec({
8 | context("toPascalCase") {
9 | listOf(
10 | "some-name" to "SomeName",
11 | "some_name" to "SomeName",
12 | "SOME_PROPERTY" to "SomeProperty",
13 | "some+name" to "SomePlusName",
14 | "some name with spaces" to "SomeNameWithSpaces",
15 | "some.name.with.dots" to "SomeNameWithDots",
16 | "some-action/some-subdirectory" to "SomeActionSomeSubdirectory",
17 | ).forEach { (input, output) ->
18 | test("should convert '$input' to '$output'") {
19 | input.toPascalCase() shouldBe output
20 | }
21 | }
22 | }
23 |
24 | context("toCamelCase") {
25 | listOf(
26 | "some-name" to "someName",
27 | "some_name" to "someName",
28 | "some+name" to "somePlusName",
29 | "SOME_NAME" to "someName",
30 | ).forEach { (input, output) ->
31 | test("should convert '$input' to '$output'") {
32 | input.toCamelCase() shouldBe output
33 | }
34 | }
35 | }
36 | })
37 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/triggers/WorkflowCall.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain.triggers
2 |
3 | import kotlinx.serialization.Contextual
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | /**
8 | * https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_call
9 | */
10 | @Serializable
11 | public data class WorkflowCall(
12 | val inputs: Map = emptyMap(),
13 | val outputs: Map? = null,
14 | val secrets: Map? = null,
15 | override val _customArguments: Map = mapOf(),
16 | ) : Trigger() {
17 | @Serializable
18 | public enum class Type {
19 | @SerialName("boolean")
20 | Boolean,
21 |
22 | @SerialName("number")
23 | Number,
24 |
25 | @SerialName("string")
26 | String,
27 | }
28 |
29 | @Serializable
30 | public class Input(
31 | public val description: String,
32 | public val required: Boolean,
33 | public val type: Type,
34 | public val default: String? = null,
35 | )
36 |
37 | @Serializable
38 | public class Output(
39 | public val description: String,
40 | public val value: String,
41 | )
42 |
43 | @Serializable
44 | public class Secret(
45 | public val description: String,
46 | public val required: Boolean,
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/dsl/expressions/PayloadTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.dsl.expressions
2 |
3 | import io.github.typesafegithub.workflows.dsl.expressions.contexts.RunnerContext
4 | import io.kotest.assertions.AssertionErrorBuilder.Companion.fail
5 | import io.kotest.core.spec.style.FunSpec
6 | import kotlinx.serialization.json.Json
7 | import kotlinx.serialization.json.jsonObject
8 | import java.io.File
9 | import kotlin.reflect.KClass
10 | import kotlin.reflect.full.declaredMemberProperties
11 |
12 | class PayloadTest :
13 | FunSpec({
14 | val payloads = File("src/test/resources/payloads")
15 |
16 | fun KClass<*>.properties(): List = declaredMemberProperties.map { it.name }.sorted()
17 |
18 | test("Runner context") {
19 | val context = RunnerContext::class
20 | val file = payloads.resolve("runner.json")
21 | val jsonObject = Json.parseToJsonElement(file.readText()).jsonObject
22 |
23 | context.properties() shouldMatch jsonObject.keys.sorted()
24 | }
25 | })
26 |
27 | infix fun List.shouldMatch(other: List) {
28 | if (this.toSet() != other.toSet()) {
29 | fail(
30 | """
31 | |The lists don't match
32 | |Missing: ${(other - toSet()).distinct().sorted()}
33 | |Unexpected: ${(this - other.toSet()).distinct().sorted()}
34 | """.trimMargin(),
35 | )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.jitbindingserver
2 |
3 | import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords
4 | import io.github.typesafegithub.workflows.actionbindinggenerator.domain.SignificantVersion
5 | import io.github.typesafegithub.workflows.actionbindinggenerator.domain.SignificantVersion.FULL
6 | import io.ktor.http.Parameters
7 |
8 | fun Parameters.extractActionCoords(extractVersion: Boolean): ActionCoords {
9 | val owner = this["owner"]!!
10 | val nameAndPathAndSignificantVersionParts = this["name"]!!.split("___", limit = 2)
11 | val nameAndPath = nameAndPathAndSignificantVersionParts.first()
12 | val significantVersion =
13 | nameAndPathAndSignificantVersionParts
14 | .drop(1)
15 | .takeIf { it.isNotEmpty() }
16 | ?.single()
17 | ?.let { significantVersionString ->
18 | SignificantVersion
19 | .entries
20 | .find { "$it" == significantVersionString }
21 | } ?: FULL
22 | val nameAndPathParts = nameAndPath.split("__")
23 | val name = nameAndPathParts.first()
24 | val path =
25 | nameAndPathParts
26 | .drop(1)
27 | .joinToString("/")
28 | .takeUnless { it.isBlank() }
29 | val version = if (extractVersion) this["version"]!! else "irrelevant"
30 |
31 | return ActionCoords(owner, name, version, significantVersion, path)
32 | }
33 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/JobOutputs.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain
2 |
3 | import io.github.typesafegithub.workflows.dsl.expressions.expr
4 | import kotlin.properties.ReadWriteProperty
5 | import kotlin.reflect.KProperty
6 |
7 | public open class JobOutputs {
8 | internal lateinit var job: Job<*>
9 | private val _outputMapping: MutableMap = mutableMapOf()
10 |
11 | public object EMPTY : JobOutputs()
12 |
13 | public val outputMapping: Map get() = _outputMapping.toMap()
14 |
15 | public fun output(): Ref = Ref()
16 |
17 | public inner class Ref : ReadWriteProperty {
18 | private var initialized: Boolean = false
19 |
20 | override fun getValue(
21 | thisRef: JobOutputs,
22 | property: KProperty<*>,
23 | ): String {
24 | val key = property.name
25 | check(initialized) {
26 | "output '$key' must be initialized"
27 | }
28 | return "needs.${job.id}.outputs.$key"
29 | }
30 |
31 | override fun setValue(
32 | thisRef: JobOutputs,
33 | property: KProperty<*>,
34 | value: String,
35 | ) {
36 | val key = property.name
37 | check(!initialized) {
38 | "Value for output '$key' can be assigned only once!"
39 | }
40 | _outputMapping[key] = expr(value)
41 | initialized = true
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/test/kotlin/io/github/typesafegithub/workflows/yaml/CaseTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.yaml
2 |
3 | import io.github.typesafegithub.workflows.domain.triggers.PullRequest
4 | import io.github.typesafegithub.workflows.domain.triggers.PullRequest.Type
5 | import io.github.typesafegithub.workflows.domain.triggers.PullRequestTarget
6 | import io.kotest.assertions.throwables.shouldThrowAny
7 | import io.kotest.core.spec.style.DescribeSpec
8 | import io.kotest.inspectors.forAll
9 | import io.kotest.matchers.shouldBe
10 |
11 | class CaseTest :
12 | DescribeSpec({
13 | it("transforms to pascal case") {
14 | listOf(
15 | Type.Assigned to "assigned",
16 | Type.AutoMergeDisabled to "auto_merge_disabled",
17 | Type.ReviewRequested to "review_requested",
18 | ).forAll { (type, expected) ->
19 | type.toSnakeCase() shouldBe expected
20 | }
21 | }
22 |
23 | it("should fail early if the enum is not in pascal case") {
24 | MyEnum.values().forAll { enum ->
25 | shouldThrowAny {
26 | enum.toSnakeCase()
27 | }
28 | }
29 | }
30 |
31 | it("all enums should be in pascal case") {
32 | PullRequestTarget.Type.values().forAll { it.toSnakeCase() }
33 | PullRequest.Type.values().forAll { it.toSnakeCase() }
34 | }
35 | })
36 |
37 | private enum class MyEnum {
38 | @Suppress("ktlint:standard:enum-entry-name-case")
39 | In_valid,
40 | }
41 |
--------------------------------------------------------------------------------
/github-workflows-kt/src/main/kotlin/io/github/typesafegithub/workflows/domain/Job.kt:
--------------------------------------------------------------------------------
1 | package io.github.typesafegithub.workflows.domain
2 |
3 | import io.github.typesafegithub.workflows.dsl.HasCustomArguments
4 | import io.github.typesafegithub.workflows.validation.requireMatchesRegex
5 | import kotlinx.serialization.Contextual
6 |
7 | public data class Job