diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..0783b89c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: krzema12 +custom: https://www.buymeacoffee.com/krzema12 diff --git a/.github/workflows/build.main.kts b/.github/workflows/build.main.kts index f52bb9bc..cfb116b7 100755 --- a/.github/workflows/build.main.kts +++ b/.github/workflows/build.main.kts @@ -1,14 +1,21 @@ #!/usr/bin/env kotlin -@file:DependsOn("it.krzeminski:github-actions-kotlin-dsl:0.34.1") +@file:Repository("https://repo1.maven.org/maven2/") +@file:DependsOn("io.github.typesafegithub:github-workflows-kt:3.2.0") -import it.krzeminski.githubactions.actions.actions.CheckoutV3 -import it.krzeminski.githubactions.actions.gradle.GradleBuildActionV2 -import it.krzeminski.githubactions.actions.krzema12.GithubActionsTypingV0 -import it.krzeminski.githubactions.domain.RunnerType -import it.krzeminski.githubactions.domain.triggers.PullRequest -import it.krzeminski.githubactions.domain.triggers.Push -import it.krzeminski.githubactions.dsl.workflow -import it.krzeminski.githubactions.yaml.writeToFile +@file:Repository("https://bindings.krzeminski.it") +@file:DependsOn("actions:checkout:v4") +@file:DependsOn("actions:setup-java:v4") +@file:DependsOn("gradle:gradle-build-action:v3") +@file:DependsOn("typesafegithub:github-actions-typing:v1") + +import io.github.typesafegithub.workflows.actions.actions.Checkout +import io.github.typesafegithub.workflows.actions.actions.SetupJava +import io.github.typesafegithub.workflows.actions.gradle.GradleBuildAction +import io.github.typesafegithub.workflows.actions.typesafegithub.GithubActionsTyping +import io.github.typesafegithub.workflows.domain.RunnerType +import io.github.typesafegithub.workflows.domain.triggers.PullRequest +import io.github.typesafegithub.workflows.domain.triggers.Push +import io.github.typesafegithub.workflows.dsl.workflow workflow( name = "Build", @@ -16,30 +23,52 @@ workflow( Push(branches = listOf("main")), PullRequest(), ), - sourceFile = __FILE__.toPath(), + sourceFile = __FILE__, ) { job( id = "build", runsOn = RunnerType.UbuntuLatest, ) { - uses(CheckoutV3()) - uses(GradleBuildActionV2(arguments = "build")) + uses(action = Checkout()) + uses(action = GradleBuildAction(arguments = "build")) } job( id = "validate-types", runsOn = RunnerType.UbuntuLatest, ) { - uses(CheckoutV3()) - uses(GithubActionsTypingV0()) + uses(action = Checkout()) + uses(action = GithubActionsTyping()) } job( - id = "build_kotlin_scripts", - name = "Build Kotlin scripts", + id = "workflows_consistency_check", + name = "Run consistency check on all GitHub workflows", runsOn = RunnerType.UbuntuLatest, ) { - uses(CheckoutV3()) - run("find -name '*.main.kts' | xargs kotlinc") + uses(action = Checkout()) + uses( + name = "Set up Java in proper version", + action = SetupJava( + javaVersion = "17", + distribution = SetupJava.Distribution.Zulu, + cache = SetupJava.BuildPlatform.Gradle, + ), + ) + run(command = "cd .github/workflows") + run( + name = "Regenerate all workflow YAMLs", + command = """ + find -name "*.main.kts" -print0 | while read -d ${'$'}'\0' file + do + echo "Regenerating ${'$'}file..." + (${'$'}file) + done + """.trimIndent(), + ) + run( + name = "Check if some file is different after regeneration", + command = "git diff --exit-code .", + ) } -}.writeToFile() +} diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9db25d2c..279ef032 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,60 +1,72 @@ # This file was generated using Kotlin DSL (.github/workflows/build.main.kts). # If you want to modify the workflow, please change the Kotlin file and regenerate this YAML file. -# Generated with https://github.com/krzema12/github-workflows-kt +# Generated with https://github.com/typesafegithub/github-workflows-kt -name: Build +name: 'Build' on: push: branches: - - main + - 'main' pull_request: {} jobs: check_yaml_consistency: - runs-on: ubuntu-latest + name: 'Check YAML consistency' + runs-on: 'ubuntu-latest' steps: - - id: step-0 - name: Check out - uses: actions/checkout@v3 - - id: step-1 - name: Set up Java in proper version - uses: actions/setup-java@v3 - with: - java-version: 17 - distribution: zulu - cache: gradle - - id: step-2 - name: Execute script - run: rm '.github/workflows/build.yaml' && '.github/workflows/build.main.kts' - - id: step-3 - name: Consistency check - run: git diff --exit-code '.github/workflows/build.yaml' + - id: 'step-0' + name: 'Check out' + uses: 'actions/checkout@v4' + - id: 'step-1' + name: 'Execute script' + run: 'rm ''.github/workflows/build.yaml'' && ''.github/workflows/build.main.kts''' + - id: 'step-2' + name: 'Consistency check' + run: 'git diff --exit-code ''.github/workflows/build.yaml''' build: - runs-on: ubuntu-latest + runs-on: 'ubuntu-latest' needs: - - check_yaml_consistency + - 'check_yaml_consistency' steps: - - id: step-0 - uses: actions/checkout@v3 - - id: step-1 - uses: gradle/gradle-build-action@v2 + - id: 'step-0' + uses: 'actions/checkout@v4' + - id: 'step-1' + uses: 'gradle/gradle-build-action@v3' with: - arguments: build + arguments: 'build' validate-types: - runs-on: ubuntu-latest + runs-on: 'ubuntu-latest' needs: - - check_yaml_consistency + - 'check_yaml_consistency' steps: - - id: step-0 - uses: actions/checkout@v3 - - id: step-1 - uses: krzema12/github-actions-typing@v0 - build_kotlin_scripts: - name: Build Kotlin scripts - runs-on: ubuntu-latest + - id: 'step-0' + uses: 'actions/checkout@v4' + - id: 'step-1' + uses: 'typesafegithub/github-actions-typing@v1' + workflows_consistency_check: + name: 'Run consistency check on all GitHub workflows' + runs-on: 'ubuntu-latest' needs: - - check_yaml_consistency + - 'check_yaml_consistency' steps: - - id: step-0 - uses: actions/checkout@v3 - - id: step-1 - run: find -name '*.main.kts' | xargs kotlinc + - id: 'step-0' + uses: 'actions/checkout@v4' + - id: 'step-1' + name: 'Set up Java in proper version' + uses: 'actions/setup-java@v4' + with: + java-version: '17' + distribution: 'zulu' + cache: 'gradle' + - id: 'step-2' + run: 'cd .github/workflows' + - id: 'step-3' + name: 'Regenerate all workflow YAMLs' + run: |- + find -name "*.main.kts" -print0 | while read -d $'\0' file + do + echo "Regenerating $file..." + ($file) + done + - id: 'step-4' + name: 'Check if some file is different after regeneration' + run: 'git diff --exit-code .' diff --git a/.github/workflows/release.main.kts b/.github/workflows/release.main.kts new file mode 100755 index 00000000..a2ed551c --- /dev/null +++ b/.github/workflows/release.main.kts @@ -0,0 +1,123 @@ +#!/usr/bin/env kotlin +@file:Repository("https://repo1.maven.org/maven2/") +@file:DependsOn("io.github.typesafegithub:github-workflows-kt:3.2.0") +@file:DependsOn("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0") + +@file:Repository("https://bindings.krzeminski.it") +@file:DependsOn("actions:checkout:v4") +@file:DependsOn("gradle:actions__setup-gradle:v4") +@file:OptIn(ExperimentalKotlinLogicStep::class) + +import io.github.typesafegithub.workflows.actions.actions.Checkout +import io.github.typesafegithub.workflows.actions.gradle.ActionsSetupGradle +import io.github.typesafegithub.workflows.annotations.ExperimentalKotlinLogicStep +import io.github.typesafegithub.workflows.domain.RunnerType +import io.github.typesafegithub.workflows.domain.triggers.WorkflowDispatch +import io.github.typesafegithub.workflows.dsl.expressions.expr +import io.github.typesafegithub.workflows.dsl.workflow +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive + +workflow( + name = "Release", + on = listOf( + WorkflowDispatch( + inputs = mapOf( + "version" to WorkflowDispatch.Input( + type = WorkflowDispatch.Type.String, + required = true, + description = "Used for the tag and the version name. E.g. v1.2.3.", + ) + ), + ), + ), + sourceFile = __FILE__, +) { + job( + id = "release", + runsOn = RunnerType.UbuntuLatest, + ) { + uses(action = Checkout()) + uses(action = ActionsSetupGradle()) + run(command = "./gradlew build") + + run( + name = "Regenerate the contents of dist directory", + command = """ + set -euxo pipefail + + rm -rf dist + unzip -qq build/distributions/github-actions-typing.zip -d dist + rm -rf dist/github-actions-typing/bin + """.trimIndent() + ) + + run( + name = "Configure git", + command = """ + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" + """.trimIndent() + ) + + val tempBranchName = "temp-branch-for-release" + + run( + name = "Commit changes", + command = """ + git checkout -b $tempBranchName + git add . + git commit -m "Update dist" + """.trimIndent() + ) + + run( + name = "Push commit", + command = "git push --set-upstream origin $tempBranchName", + ) + + val versionExpr = expr { "github.event.inputs.version" } + + run( + name = "Create and push a patch version tag", + command = """ + git tag -a "$versionExpr" -m "Release version $versionExpr" + git push origin "$versionExpr" + """.trimIndent() + ) + + val MAJOR_VERSION_OUTPUT_NAME = "majorVersion" + + val extractMajorVersion = run { + // There should be a way to access the inputs using the DSL. + // TODO: https://github.com/typesafegithub/github-workflows-kt/issues/1685 + val githubContextJson = System.getenv("GHWKT_GITHUB_CONTEXT_JSON")!! + val version: String = Json.parseToJsonElement(githubContextJson) + .jsonObject["event"] + ?.jsonObject?.get("inputs") + ?.jsonObject?.get("version") + ?.jsonPrimitive?.contentOrNull + ?: error("Version couldn't be extracted from input") + val majorVersion = version.substringBefore(".") + outputs[MAJOR_VERSION_OUTPUT_NAME] = majorVersion + } + + val majorVersionExpr = expr { "steps.${extractMajorVersion.id}.outputs.$MAJOR_VERSION_OUTPUT_NAME" } + + run( + name = "Create or update a major version branch", + command = """ + git branch -D "$majorVersionExpr" || true + git checkout -b "$majorVersionExpr" + git push origin "$majorVersionExpr" -f + """.trimIndent() + ) + + run( + name = "Delete temp branch", + command = "git push origin --delete $tempBranchName" + ) + } +} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..df2aca61 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,77 @@ +# This file was generated using Kotlin DSL (.github/workflows/release.main.kts). +# If you want to modify the workflow, please change the Kotlin file and regenerate this YAML file. +# Generated with https://github.com/typesafegithub/github-workflows-kt + +name: 'Release' +on: + workflow_dispatch: + inputs: + version: + description: 'Used for the tag and the version name. E.g. v1.2.3.' + type: 'string' + required: true +jobs: + check_yaml_consistency: + name: 'Check YAML consistency' + runs-on: 'ubuntu-latest' + steps: + - id: 'step-0' + name: 'Check out' + uses: 'actions/checkout@v4' + - id: 'step-1' + name: 'Execute script' + run: 'rm ''.github/workflows/release.yaml'' && ''.github/workflows/release.main.kts''' + - id: 'step-2' + name: 'Consistency check' + run: 'git diff --exit-code ''.github/workflows/release.yaml''' + release: + runs-on: 'ubuntu-latest' + needs: + - 'check_yaml_consistency' + steps: + - id: 'step-0' + uses: 'actions/checkout@v4' + - id: 'step-1' + uses: 'gradle/actions/setup-gradle@v4' + - id: 'step-2' + run: './gradlew build' + - id: 'step-3' + name: 'Regenerate the contents of dist directory' + run: |- + set -euxo pipefail + + rm -rf dist + unzip -qq build/distributions/github-actions-typing.zip -d dist + rm -rf dist/github-actions-typing/bin + - id: 'step-4' + name: 'Configure git' + run: |- + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" + - id: 'step-5' + name: 'Commit changes' + run: |- + git checkout -b temp-branch-for-release + git add . + git commit -m "Update dist" + - id: 'step-6' + name: 'Push commit' + run: 'git push --set-upstream origin temp-branch-for-release' + - id: 'step-7' + name: 'Create and push a patch version tag' + run: |- + git tag -a "${{ github.event.inputs.version }}" -m "Release version ${{ github.event.inputs.version }}" + git push origin "${{ github.event.inputs.version }}" + - id: 'step-8' + env: + GHWKT_GITHUB_CONTEXT_JSON: '${{ toJSON(github) }}' + run: 'GHWKT_RUN_STEP=''release:step-8'' ''.github/workflows/release.main.kts''' + - id: 'step-9' + name: 'Create or update a major version branch' + run: |- + git branch -D "${{ steps.step-8.outputs.majorVersion }}" || true + git checkout -b "${{ steps.step-8.outputs.majorVersion }}" + git push origin "${{ steps.step-8.outputs.majorVersion }}" -f + - id: 'step-10' + name: 'Delete temp branch' + run: 'git push origin --delete temp-branch-for-release' diff --git a/Dockerfile b/Dockerfile index 12cde46a..14d7ec2d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,6 @@ -FROM gradle:7.6.0-jdk17 AS build -COPY --chown=gradle:gradle . /src -WORKDIR /src -RUN gradle build --no-daemon -RUN rm -rf dist -RUN unzip build/distributions/github-actions-typing.zip -d dist - -FROM eclipse-temurin:17.0.5_8-jre +FROM eclipse-temurin:21.0.5_11-jre RUN mkdir /app -COPY --from=build /src/dist/github-actions-typing/lib/*.jar /app/ +COPY dist/github-actions-typing/lib/*.jar /app/ WORKDIR /app ENTRYPOINT ["java", "-cp", "/app/*", "it.krzeminski.githubactionstyping.MainKt"] diff --git a/README.md b/README.md index db13241e..3adf1b86 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,12 @@ Bring type-safety to your GitHub actions' API! This is a GitHub action that validates your action's type specs (`action-types.y(a)ml`) and ensures the inputs and outputs have types set according to a certain specification. -It aims to be a standardized way to present your actions' API, both to human users and any kind of automation (e. g. generating action wrappers for [this Kotlin DSL](https://github.com/krzema12/github-workflows-kt)). +It aims to be a standardized way to present your actions' API, both to human users and any kind of automation (e. g. generating action bindings for [this Kotlin DSL](https://github.com/typesafegithub/github-workflows-kt)). +Similar to typings for TypeScript or type hints for Python. It supports YAML anchors and aliases to reduce duplication. -To see which actions already provide typings using this schema, click [here](https://github.com/krzema12/github-actions-typing/network/dependents). +To see which actions already provide typings using this schema, click [here](https://github.com/typesafegithub/github-actions-typing/network/dependents). # Example @@ -66,8 +67,8 @@ jobs: validate-typings: runs-on: "ubuntu-latest" steps: - - uses: actions/checkout@v3 - - uses: krzema12/github-actions-typing@v0 + - uses: actions/checkout@v4 + - uses: typesafegithub/github-actions-typing@v1 ``` ## Available types @@ -126,6 +127,19 @@ inputs: ... ``` +You can also optionally define `name` which is a hint for code generators what name can be used as class name. + +```yaml +... +inputs: + fetch-depth: + type: integer + name: Amount + named-values: + infinite: 0 + ... +``` + ### Float A number with a fractional component. diff --git a/build.gradle.kts b/build.gradle.kts index fe7bb840..b3809398 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - kotlin("jvm") version "1.8.0" - kotlin("plugin.serialization") version "1.8.0" + kotlin("jvm") version "2.1.0" + kotlin("plugin.serialization") version "2.1.0" application } @@ -9,13 +9,17 @@ repositories { } dependencies { - implementation("com.charleskorn.kaml:kaml:0.49.0") + implementation("com.charleskorn.kaml:kaml:0.63.0") - testImplementation(platform("io.kotest:kotest-bom:5.5.4")) + testImplementation(platform("io.kotest:kotest-bom:5.9.1")) testImplementation("io.kotest:kotest-runner-junit5") testImplementation("io.kotest:kotest-assertions-core") } +kotlin { + jvmToolchain(21) +} + tasks.jar { manifest.attributes["Main-Class"] = "MainKt" } diff --git a/dist/github-actions-typing/lib/annotations-13.0.jar b/dist/github-actions-typing/lib/annotations-13.0.jar new file mode 100644 index 00000000..fb794be9 Binary files /dev/null and b/dist/github-actions-typing/lib/annotations-13.0.jar differ diff --git a/dist/github-actions-typing/lib/github-actions-typing.jar b/dist/github-actions-typing/lib/github-actions-typing.jar new file mode 100644 index 00000000..21dfd401 Binary files /dev/null and b/dist/github-actions-typing/lib/github-actions-typing.jar differ diff --git a/dist/github-actions-typing/lib/kaml-jvm-0.63.0.jar b/dist/github-actions-typing/lib/kaml-jvm-0.63.0.jar new file mode 100644 index 00000000..0991f0da Binary files /dev/null and b/dist/github-actions-typing/lib/kaml-jvm-0.63.0.jar differ diff --git a/dist/github-actions-typing/lib/kotlin-stdlib-2.1.0.jar b/dist/github-actions-typing/lib/kotlin-stdlib-2.1.0.jar new file mode 100644 index 00000000..49fb3891 Binary files /dev/null and b/dist/github-actions-typing/lib/kotlin-stdlib-2.1.0.jar differ diff --git a/dist/github-actions-typing/lib/kotlinx-serialization-core-jvm-1.7.3.jar b/dist/github-actions-typing/lib/kotlinx-serialization-core-jvm-1.7.3.jar new file mode 100644 index 00000000..96a1308d Binary files /dev/null and b/dist/github-actions-typing/lib/kotlinx-serialization-core-jvm-1.7.3.jar differ diff --git a/dist/github-actions-typing/lib/okio-jvm-3.9.1.jar b/dist/github-actions-typing/lib/okio-jvm-3.9.1.jar new file mode 100644 index 00000000..211e015b Binary files /dev/null and b/dist/github-actions-typing/lib/okio-jvm-3.9.1.jar differ diff --git a/dist/github-actions-typing/lib/snakeyaml-engine-kmp-jvm-3.0.3.jar b/dist/github-actions-typing/lib/snakeyaml-engine-kmp-jvm-3.0.3.jar new file mode 100644 index 00000000..5787fa0b Binary files /dev/null and b/dist/github-actions-typing/lib/snakeyaml-engine-kmp-jvm-3.0.3.jar differ diff --git a/dist/github-actions-typing/lib/urlencoder-lib-jvm-1.6.0.jar b/dist/github-actions-typing/lib/urlencoder-lib-jvm-1.6.0.jar new file mode 100644 index 00000000..fc792fbe Binary files /dev/null and b/dist/github-actions-typing/lib/urlencoder-lib-jvm-1.6.0.jar differ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbf..a4b76b95 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index adb6acbd..e1b837a1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=7ba68c54029790ab444b39d7e293d3236b2632631fb5f2e012bb28b4ff669e4b -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionSha256Sum=7a00d51fb93147819aab76024feece20b6b84e420694101f276be952e08bef03 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d..f3b75f3b 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,10 +85,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +133,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +200,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85b..9b42019c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/renovate.json b/renovate.json index 39a2b6e9..afdfacc6 100644 --- a/renovate.json +++ b/renovate.json @@ -2,5 +2,6 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base" - ] + ], + "automerge": true } diff --git a/src/main/kotlin/it/krzeminski/githubactionstyping/Logic.kt b/src/main/kotlin/it/krzeminski/githubactionstyping/Logic.kt new file mode 100644 index 00000000..6b335eaa --- /dev/null +++ b/src/main/kotlin/it/krzeminski/githubactionstyping/Logic.kt @@ -0,0 +1,25 @@ +package it.krzeminski.githubactionstyping + +import it.krzeminski.githubactionstyping.parsing.readYamlFile +import java.nio.file.Path +import kotlin.io.path.exists + +/** + * Runs validation for a given action, with its manifest files present in the current directory. + * + * @param repoRoot: Allows customizing which path should be taken as repo root for action(-types).y(a)ml file discovery. + * + * @return a pair where: + * - the boolean means if the typings are valid + * - the string is a printable report, with details about all inputs and outputs + */ +fun validateTypings(repoRoot: Path = Path.of(".")): Pair { + require(repoRoot.exists()) { "The given repo root leads to non-existent dir: $repoRoot" } + val (manifest, manifestPath) = repoRoot.readYamlFile("action") ?: + return Pair(false, "No action manifest (action.yml or action.yaml) found!") + + val (typesManifest, _) = repoRoot.readYamlFile("action-types") ?: + return Pair(false, "No types manifest (action-types.yml or action-types.yaml) found!") + + return manifestsToReport(repoRoot.relativize(manifestPath), manifest, typesManifest) +} diff --git a/src/main/kotlin/it/krzeminski/githubactionstyping/Main.kt b/src/main/kotlin/it/krzeminski/githubactionstyping/Main.kt index 858a052b..46636198 100644 --- a/src/main/kotlin/it/krzeminski/githubactionstyping/Main.kt +++ b/src/main/kotlin/it/krzeminski/githubactionstyping/Main.kt @@ -1,22 +1,9 @@ package it.krzeminski.githubactionstyping -import it.krzeminski.githubactionstyping.parsing.readYamlFile import kotlin.system.exitProcess fun main() { - val typesManifest = readYamlFile("action-types") ?: run { - println("No types manifest (action-types.yml or action-types.yaml) found!") - exitProcess(1) - throw IllegalStateException() - } - - val manifest = readYamlFile("action") ?: run { - println("No action manifest (action.yml or action.yaml) found!") - exitProcess(1) - throw IllegalStateException() - } - - val (isValid, report) = manifestsToReport(manifest, typesManifest) + val (isValid, report) = validateTypings() println(report) if (!isValid) { diff --git a/src/main/kotlin/it/krzeminski/githubactionstyping/ManifestsToReport.kt b/src/main/kotlin/it/krzeminski/githubactionstyping/ManifestsToReport.kt index 38d2a09f..a459b368 100644 --- a/src/main/kotlin/it/krzeminski/githubactionstyping/ManifestsToReport.kt +++ b/src/main/kotlin/it/krzeminski/githubactionstyping/ManifestsToReport.kt @@ -7,8 +7,9 @@ import it.krzeminski.githubactionstyping.reporting.toPlaintextReport import it.krzeminski.githubactionstyping.validation.ItemValidationResult import it.krzeminski.githubactionstyping.validation.buildInputOutputMismatchValidationResult import it.krzeminski.githubactionstyping.validation.validate +import java.nio.file.Path -fun manifestsToReport(manifest: String, typesManifest: String): Pair { +fun manifestsToReport(manifestPath: Path, manifest: String, typesManifest: String): Pair { val parsedTypesManifest = if (typesManifest.isNotBlank()) { parseTypesManifest(typesManifest) } else { @@ -24,6 +25,7 @@ fun manifestsToReport(manifest: String, typesManifest: String): Pair(manifestString) + }.getOrElse { + if (it is EmptyYamlDocumentException) { + return TypesManifest() + } + throw it + } diff --git a/src/main/kotlin/it/krzeminski/githubactionstyping/parsing/TypesManifestReading.kt b/src/main/kotlin/it/krzeminski/githubactionstyping/parsing/TypesManifestReading.kt index 980f1fe8..e6292570 100644 --- a/src/main/kotlin/it/krzeminski/githubactionstyping/parsing/TypesManifestReading.kt +++ b/src/main/kotlin/it/krzeminski/githubactionstyping/parsing/TypesManifestReading.kt @@ -1,9 +1,11 @@ package it.krzeminski.githubactionstyping.parsing -import java.io.File +import java.nio.file.Path +import kotlin.io.path.exists +import kotlin.io.path.readText -fun readYamlFile(nameWithoutExtension: String): String? = +fun Path.readYamlFile(nameWithoutExtension: String): Pair? = listOf("yaml", "yml") .map { "$nameWithoutExtension.$it" } - .firstOrNull { File(it).exists() } - ?.let { File(it).readText() } + .firstOrNull { this.resolve(it).exists() } + ?.let { this.resolve(it).let { Pair(it.readText(), it) } } diff --git a/src/main/kotlin/it/krzeminski/githubactionstyping/reporting/PlaintextReport.kt b/src/main/kotlin/it/krzeminski/githubactionstyping/reporting/PlaintextReport.kt index e3a3a482..25aefd76 100644 --- a/src/main/kotlin/it/krzeminski/githubactionstyping/reporting/PlaintextReport.kt +++ b/src/main/kotlin/it/krzeminski/githubactionstyping/reporting/PlaintextReport.kt @@ -1,34 +1,37 @@ package it.krzeminski.githubactionstyping.reporting -import it.krzeminski.githubactionstyping.validation.ActionValidationResult import it.krzeminski.githubactionstyping.validation.ItemValidationResult +import it.krzeminski.githubactionstyping.validation.RepoValidationResult -fun ActionValidationResult.toPlaintextReport(): String = buildString { - appendLine("Overall result: ") - this@toPlaintextReport.overallResult.appendStatus(this) - appendLine() +fun RepoValidationResult.toPlaintextReport(): String = buildString { + this@toPlaintextReport.pathToActionValidationResult.forEach { (path, resultForAction) -> + appendLine("For action with manifest at '$path':") + appendLine("Result:") + resultForAction.overallResult.appendStatus(this) + appendLine() - appendLine("Inputs:") - this@toPlaintextReport.inputs.forEach { (key, value) -> - appendLine("• $key:") - append(" ") - value.appendStatus(this) - } - if (this@toPlaintextReport.inputs.isEmpty()) { - appendLine("None.") - } - appendLine() + appendLine("Inputs:") + resultForAction.inputs.forEach { (key, value) -> + appendLine("• $key:") + append(" ") + value.appendStatus(this) + } + if (resultForAction.inputs.isEmpty()) { + appendLine("None.") + } + appendLine() - appendLine("Outputs:") - this@toPlaintextReport.outputs.forEach { (key, value) -> - appendLine("• $key:") - append(" ") - value.appendStatus(this) - } - if (this@toPlaintextReport.outputs.isEmpty()) { - appendLine("None.") + appendLine("Outputs:") + resultForAction.outputs.forEach { (key, value) -> + appendLine("• $key:") + append(" ") + value.appendStatus(this) + } + if (resultForAction.outputs.isEmpty()) { + appendLine("None.") + } + appendLine() } - appendLine() } private fun ItemValidationResult.appendStatus( diff --git a/src/main/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidation.kt b/src/main/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidation.kt index 724a98ae..3dba2de5 100644 --- a/src/main/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidation.kt +++ b/src/main/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidation.kt @@ -8,54 +8,64 @@ import it.krzeminski.githubactionstyping.validation.types.validateFloat import it.krzeminski.githubactionstyping.validation.types.validateInteger import it.krzeminski.githubactionstyping.validation.types.validateList import it.krzeminski.githubactionstyping.validation.types.validateString +import java.nio.file.Path -fun TypesManifest.validate(): ActionValidationResult { +fun TypesManifest.validate(manifestPath: Path): RepoValidationResult { val inputValidationResults = this.inputs.mapValues { (_, value) -> value.validate() } val outputValidationResults = this.outputs.mapValues { (_, value) -> value.validate() } val isSomethingInvalid = (inputValidationResults.values + outputValidationResults.values) .any { it != ItemValidationResult.Valid } - return ActionValidationResult( + return RepoValidationResult( overallResult = if (isSomethingInvalid) ItemValidationResult.Invalid("Some typing is invalid.") else ItemValidationResult.Valid, - inputs = inputValidationResults, - outputs = outputValidationResults, + pathToActionValidationResult = mapOf(manifestPath to ActionValidationResult( + overallResult = if (isSomethingInvalid) ItemValidationResult.Invalid("Some typing is invalid.") else ItemValidationResult.Valid, + inputs = inputValidationResults, + outputs = outputValidationResults, + ) + ) ) } fun buildInputOutputMismatchValidationResult( + manifestPath: Path, inputsInManifest: Set, inputsInTypesManifest: Set, outputsInManifest: Set, outputsInTypesManifest: Set, -): ActionValidationResult { - return ActionValidationResult( - overallResult = ItemValidationResult.Invalid( - "Input/output mismatch detected. Please fix it first, then rerun to see other possible violations.", - ), - inputs = (inputsInManifest + inputsInTypesManifest) - .associateWith { - if (it in inputsInManifest && it in inputsInTypesManifest) { - ItemValidationResult.Valid - } else { - if (it !in inputsInManifest) { - ItemValidationResult.Invalid("This input doesn't exist in the action manifest.") +): RepoValidationResult { + return RepoValidationResult( + overallResult = ItemValidationResult.Invalid("There was input/output mismatch for one of the actions."), + pathToActionValidationResult = mapOf(manifestPath to ActionValidationResult( + overallResult = ItemValidationResult.Invalid( + "Input/output mismatch detected. Please fix it first, then rerun to see other possible violations.", + ), + inputs = (inputsInManifest + inputsInTypesManifest) + .associateWith { + if (it in inputsInManifest && it in inputsInTypesManifest) { + ItemValidationResult.Valid } else { - ItemValidationResult.Invalid("This input doesn't exist in the types manifest.") + if (it !in inputsInManifest) { + ItemValidationResult.Invalid("This input doesn't exist in the action manifest.") + } else { + ItemValidationResult.Invalid("This input doesn't exist in the types manifest.") + } } - } - }, - outputs = (outputsInManifest + outputsInTypesManifest) - .associateWith { - if (it in outputsInManifest && it in outputsInTypesManifest) { - ItemValidationResult.Valid - } else { - if (it !in outputsInManifest) { - ItemValidationResult.Invalid("This output doesn't exist in the action manifest.") + }, + outputs = (outputsInManifest + outputsInTypesManifest) + .associateWith { + if (it in outputsInManifest && it in outputsInTypesManifest) { + ItemValidationResult.Valid } else { - ItemValidationResult.Invalid("This output doesn't exist in the types manifest.") + if (it !in outputsInManifest) { + ItemValidationResult.Invalid("This output doesn't exist in the action manifest.") + } else { + ItemValidationResult.Invalid("This output doesn't exist in the types manifest.") + } } - } - }, + }, + ) + ) ) } @@ -74,14 +84,3 @@ private fun ApiItem.validate(): ItemValidationResult { else -> ItemValidationResult.Invalid("Unknown type: '${this.type}'.") } } - -data class ActionValidationResult( - val overallResult: ItemValidationResult, - val inputs: Map = emptyMap(), - val outputs: Map = emptyMap(), -) - -sealed interface ItemValidationResult { - object Valid : ItemValidationResult - data class Invalid(val message: String) : ItemValidationResult -} diff --git a/src/main/kotlin/it/krzeminski/githubactionstyping/validation/Model.kt b/src/main/kotlin/it/krzeminski/githubactionstyping/validation/Model.kt new file mode 100644 index 00000000..fc61b396 --- /dev/null +++ b/src/main/kotlin/it/krzeminski/githubactionstyping/validation/Model.kt @@ -0,0 +1,19 @@ +package it.krzeminski.githubactionstyping.validation + +import java.nio.file.Path + +data class RepoValidationResult( + val overallResult: ItemValidationResult, + val pathToActionValidationResult: Map, +) + +data class ActionValidationResult( + val overallResult: ItemValidationResult, + val inputs: Map = emptyMap(), + val outputs: Map = emptyMap(), +) + +sealed interface ItemValidationResult { + data object Valid : ItemValidationResult + data class Invalid(val message: String) : ItemValidationResult +} diff --git a/src/main/kotlin/it/krzeminski/githubactionstyping/validation/types/Integer.kt b/src/main/kotlin/it/krzeminski/githubactionstyping/validation/types/Integer.kt index cf9b8d1c..3187d3b6 100644 --- a/src/main/kotlin/it/krzeminski/githubactionstyping/validation/types/Integer.kt +++ b/src/main/kotlin/it/krzeminski/githubactionstyping/validation/types/Integer.kt @@ -4,8 +4,8 @@ import it.krzeminski.githubactionstyping.parsing.ApiItem import it.krzeminski.githubactionstyping.validation.ItemValidationResult fun ApiItem.validateInteger(): ItemValidationResult { - if (this.name != null) { - return ItemValidationResult.Invalid("'name' is not allowed for this type.") + if ((this.namedValues == null) && (this.name != null)) { + return ItemValidationResult.Invalid("'name' is only allowed for this type when also having 'named-values'.") } if (this.allowedValues != null) { return ItemValidationResult.Invalid("'allowed-values' is not allowed for this type.") diff --git a/src/test/kotlin/it/krzeminski/githubactionstyping/LogicTest.kt b/src/test/kotlin/it/krzeminski/githubactionstyping/LogicTest.kt new file mode 100644 index 00000000..2c66c016 --- /dev/null +++ b/src/test/kotlin/it/krzeminski/githubactionstyping/LogicTest.kt @@ -0,0 +1,95 @@ +package it.krzeminski.githubactionstyping + +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import java.io.File +import java.nio.file.Path + +class LogicTest : FunSpec({ + val testRepos: Path = javaClass.classLoader.getResource("test-repos").let { + File(it.toURI()).toPath() + } + + test("repo with only top-level action and valid typings") { + // When + val (isValid, report) = validateTypings( + repoRoot = testRepos.resolve("repo-with-only-top-level-action-and-valid-typings"), + ) + + // Then + assertSoftly { + isValid shouldBe true + report shouldBe """ + For action with manifest at 'action.yml': + Result: + ${'\u001b'}[32m✔ VALID${'\u001b'}[0m + + Inputs: + • verbose: + ${'\u001b'}[32m✔ VALID${'\u001b'}[0m + • someEnum: + ${'\u001b'}[32m✔ VALID${'\u001b'}[0m + + Outputs: + None. + + + """.trimIndent() + } + } + + test("repo with only top-level action and invalid typings") { + // When + val (isValid, report) = validateTypings( + repoRoot = testRepos.resolve("repo-with-only-top-level-action-and-invalid-typings"), + ) + + // Then + assertSoftly { + isValid shouldBe false + report shouldBe """ + For action with manifest at 'action.yml': + Result: + ${'\u001b'}[31m❌ INVALID: Some typing is invalid.${'\u001b'}[0m + + Inputs: + • verbose: + ${'\u001b'}[32m✔ VALID${'\u001b'}[0m + • someEnum: + ${'\u001b'}[31m❌ INVALID: 'allowed-values' is not allowed for this type.${'\u001b'}[0m + + Outputs: + None. + + + """.trimIndent() + } + } + + test("repo with only top-level action and no typings") { + // When + val (isValid, report) = validateTypings( + repoRoot = testRepos.resolve("repo-with-only-top-level-action-and-no-typings"), + ) + + // Then + assertSoftly { + isValid shouldBe false + report shouldBe "No types manifest (action-types.yml or action-types.yaml) found!" + } + } + + test("repo with only top-level action and top-level manifest") { + // When + val (isValid, report) = validateTypings( + repoRoot = testRepos.resolve("repo-with-only-top-level-action-and-no-top-level-manifest"), + ) + + // Then + assertSoftly { + isValid shouldBe false + report shouldBe "No action manifest (action.yml or action.yaml) found!" + } + } +}) diff --git a/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidationTest.kt b/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidationTest.kt index a60c5dad..f1785006 100644 --- a/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidationTest.kt +++ b/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidationTest.kt @@ -4,6 +4,7 @@ import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import it.krzeminski.githubactionstyping.parsing.ApiItem import it.krzeminski.githubactionstyping.parsing.TypesManifest +import kotlin.io.path.Path class ManifestValidationTest : FunSpec({ context("success cases") { @@ -14,13 +15,21 @@ class ManifestValidationTest : FunSpec({ "string-input" to ApiItem(type = "string"), "boolean-input" to ApiItem(type = "boolean"), "integer-input" to ApiItem(type = "integer"), - "integer-with-named-values-input" to ApiItem(type = "integer", namedValues = mapOf("foo" to 1, "bar" to 2)), + "integer-with-named-values-input" to ApiItem( + type = "integer", + namedValues = mapOf("foo" to 1, "bar" to 2) + ), + "integer-with-named-values-and-custom-item-name-input" to ApiItem( + type = "integer", + name = "SomeItemName", + namedValues = mapOf("foo" to 1, "bar" to 2) + ), "float-input" to ApiItem(type = "float"), ), ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -30,6 +39,7 @@ class ManifestValidationTest : FunSpec({ "boolean-input" to ItemValidationResult.Valid, "integer-input" to ItemValidationResult.Valid, "integer-with-named-values-input" to ItemValidationResult.Valid, + "integer-with-named-values-and-custom-item-name-input" to ItemValidationResult.Valid, "float-input" to ItemValidationResult.Valid, ), ) @@ -49,7 +59,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -97,7 +107,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -126,7 +136,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -153,7 +163,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -178,7 +188,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -204,7 +214,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -235,7 +245,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -259,7 +269,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -279,7 +289,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -299,7 +309,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -319,7 +329,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -339,7 +349,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -364,7 +374,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -392,7 +402,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -428,7 +438,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -460,7 +470,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -488,7 +498,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -496,7 +506,7 @@ class ManifestValidationTest : FunSpec({ inputs = mapOf( "string-input" to ItemValidationResult.Invalid("'name' is not allowed for this type."), "boolean-input" to ItemValidationResult.Invalid("'name' is not allowed for this type."), - "integer-input" to ItemValidationResult.Invalid("'name' is not allowed for this type."), + "integer-input" to ItemValidationResult.Invalid("'name' is only allowed for this type when also having 'named-values'."), "float-input" to ItemValidationResult.Invalid("'name' is not allowed for this type."), "list-input" to ItemValidationResult.Invalid("'name' is not allowed for this type."), ), diff --git a/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestsToReportTest.kt b/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestsToReportTest.kt index 0adf1ae6..e4cee166 100644 --- a/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestsToReportTest.kt +++ b/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestsToReportTest.kt @@ -4,6 +4,7 @@ import io.kotest.assertions.assertSoftly import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import it.krzeminski.githubactionstyping.manifestsToReport +import kotlin.io.path.Path class ManifestsToReportTest : FunSpec({ test("success case") { @@ -36,13 +37,14 @@ class ManifestsToReportTest : FunSpec({ """.trimIndent() // when - val (isValid, report) = manifestsToReport(manifest, typesManifest) + val (isValid, report) = manifestsToReport(Path("action.yml"), manifest, typesManifest) // then assertSoftly { isValid shouldBe true report shouldBe """ - Overall result: + For action with manifest at 'action.yml': + Result: ${'\u001b'}[32m✔ VALID${'\u001b'}[0m Inputs: @@ -95,13 +97,14 @@ class ManifestsToReportTest : FunSpec({ """.trimIndent() // when - val (isValid, report) = manifestsToReport(manifest, typesManifest) + val (isValid, report) = manifestsToReport(Path("action.yml"), manifest, typesManifest) // then assertSoftly { isValid shouldBe true report shouldBe """ - Overall result: + For action with manifest at 'action.yml': + Result: ${'\u001b'}[32m✔ VALID${'\u001b'}[0m Inputs: @@ -133,13 +136,48 @@ class ManifestsToReportTest : FunSpec({ val typesManifest = " " // when - val (isValid, report) = manifestsToReport(manifest, typesManifest) + val (isValid, report) = manifestsToReport(Path("action.yml"), manifest, typesManifest) // then assertSoftly { isValid shouldBe true report shouldBe """ - Overall result: + For action with manifest at 'action.yml': + Result: + ${'\u001b'}[32m✔ VALID${'\u001b'}[0m + + Inputs: + None. + + Outputs: + None. + + + """.trimIndent() + } + } + + test("comment-only types YAML") { + // when + val manifest = """ + name: GitHub Actions Typing + description: Bring type-safety to your GitHub actions' API! + author: Piotr Krzemiński + runs: + using: 'docker' + image: 'Dockerfile' + """.trimIndent() + val typesManifest = "#" + + // when + val (isValid, report) = manifestsToReport(Path("action.yml"), manifest, typesManifest) + + // then + assertSoftly { + isValid shouldBe true + report shouldBe """ + For action with manifest at 'action.yml': + Result: ${'\u001b'}[32m✔ VALID${'\u001b'}[0m Inputs: @@ -180,13 +218,14 @@ class ManifestsToReportTest : FunSpec({ """.trimIndent() // when - val (isValid, report) = manifestsToReport(manifest, typesManifest) + val (isValid, report) = manifestsToReport(Path("action.yml"), manifest, typesManifest) // then assertSoftly { isValid shouldBe false report shouldBe """ - Overall result: + For action with manifest at 'action.yml': + Result: ${'\u001b'}[31m❌ INVALID: Some typing is invalid.${'\u001b'}[0m Inputs: @@ -239,13 +278,14 @@ class ManifestsToReportTest : FunSpec({ """.trimIndent() // when - val (isValid, report) = manifestsToReport(manifest, typesManifest) + val (isValid, report) = manifestsToReport(Path("action.yml"), manifest, typesManifest) // then assertSoftly { isValid shouldBe false report shouldBe """ - Overall result: + For action with manifest at 'action.yml': + Result: ${'\u001b'}[31m❌ INVALID: Input/output mismatch detected. Please fix it first, then rerun to see other possible violations.${'\u001b'}[0m Inputs: diff --git a/src/test/resources/test-repos/repo-with-only-top-level-action-and-invalid-typings/README.md b/src/test/resources/test-repos/repo-with-only-top-level-action-and-invalid-typings/README.md new file mode 100644 index 00000000..53fc8f7d --- /dev/null +++ b/src/test/resources/test-repos/repo-with-only-top-level-action-and-invalid-typings/README.md @@ -0,0 +1 @@ +I'm an action with just a single top-level "action.yml" and its associated typings that are invalid. \ No newline at end of file diff --git a/src/test/resources/test-repos/repo-with-only-top-level-action-and-invalid-typings/action-types.yml b/src/test/resources/test-repos/repo-with-only-top-level-action-and-invalid-typings/action-types.yml new file mode 100644 index 00000000..f6a7f36f --- /dev/null +++ b/src/test/resources/test-repos/repo-with-only-top-level-action-and-invalid-typings/action-types.yml @@ -0,0 +1,8 @@ +inputs: + verbose: + type: boolean + someEnum: + type: string + allowed-values: + - foo + - bar diff --git a/src/test/resources/test-repos/repo-with-only-top-level-action-and-invalid-typings/action.yml b/src/test/resources/test-repos/repo-with-only-top-level-action-and-invalid-typings/action.yml new file mode 100644 index 00000000..f3525c43 --- /dev/null +++ b/src/test/resources/test-repos/repo-with-only-top-level-action-and-invalid-typings/action.yml @@ -0,0 +1,14 @@ +name: GitHub Actions Typing +description: Bring type-safety to your GitHub actions' API! +author: Piotr Krzemiński +inputs: + verbose: + description: 'Set to true to display debug information helpful when troubleshooting issues with this action.' + required: false + default: 'false' + someEnum: + description: 'Testing enum' + required: false +runs: + using: 'docker' + image: 'Dockerfile' diff --git a/src/test/resources/test-repos/repo-with-only-top-level-action-and-no-top-level-manifest/README.md b/src/test/resources/test-repos/repo-with-only-top-level-action-and-no-top-level-manifest/README.md new file mode 100644 index 00000000..5ba27c1f --- /dev/null +++ b/src/test/resources/test-repos/repo-with-only-top-level-action-and-no-top-level-manifest/README.md @@ -0,0 +1 @@ +I'm an action with just a single top-level "action.yml" and no associated typings. \ No newline at end of file diff --git a/src/test/resources/test-repos/repo-with-only-top-level-action-and-no-typings/README.md b/src/test/resources/test-repos/repo-with-only-top-level-action-and-no-typings/README.md new file mode 100644 index 00000000..5ba27c1f --- /dev/null +++ b/src/test/resources/test-repos/repo-with-only-top-level-action-and-no-typings/README.md @@ -0,0 +1 @@ +I'm an action with just a single top-level "action.yml" and no associated typings. \ No newline at end of file diff --git a/src/test/resources/test-repos/repo-with-only-top-level-action-and-no-typings/action.yml b/src/test/resources/test-repos/repo-with-only-top-level-action-and-no-typings/action.yml new file mode 100644 index 00000000..f3525c43 --- /dev/null +++ b/src/test/resources/test-repos/repo-with-only-top-level-action-and-no-typings/action.yml @@ -0,0 +1,14 @@ +name: GitHub Actions Typing +description: Bring type-safety to your GitHub actions' API! +author: Piotr Krzemiński +inputs: + verbose: + description: 'Set to true to display debug information helpful when troubleshooting issues with this action.' + required: false + default: 'false' + someEnum: + description: 'Testing enum' + required: false +runs: + using: 'docker' + image: 'Dockerfile' diff --git a/src/test/resources/test-repos/repo-with-only-top-level-action-and-valid-typings/README.md b/src/test/resources/test-repos/repo-with-only-top-level-action-and-valid-typings/README.md new file mode 100644 index 00000000..8871df50 --- /dev/null +++ b/src/test/resources/test-repos/repo-with-only-top-level-action-and-valid-typings/README.md @@ -0,0 +1 @@ +I'm an action with just a single top-level "action.yml" and its associated typings that are valid. \ No newline at end of file diff --git a/src/test/resources/test-repos/repo-with-only-top-level-action-and-valid-typings/action-types.yml b/src/test/resources/test-repos/repo-with-only-top-level-action-and-valid-typings/action-types.yml new file mode 100644 index 00000000..d683ace9 --- /dev/null +++ b/src/test/resources/test-repos/repo-with-only-top-level-action-and-valid-typings/action-types.yml @@ -0,0 +1,8 @@ +inputs: + verbose: + type: boolean + someEnum: + type: enum + allowed-values: + - foo + - bar diff --git a/src/test/resources/test-repos/repo-with-only-top-level-action-and-valid-typings/action.yml b/src/test/resources/test-repos/repo-with-only-top-level-action-and-valid-typings/action.yml new file mode 100644 index 00000000..f3525c43 --- /dev/null +++ b/src/test/resources/test-repos/repo-with-only-top-level-action-and-valid-typings/action.yml @@ -0,0 +1,14 @@ +name: GitHub Actions Typing +description: Bring type-safety to your GitHub actions' API! +author: Piotr Krzemiński +inputs: + verbose: + description: 'Set to true to display debug information helpful when troubleshooting issues with this action.' + required: false + default: 'false' + someEnum: + description: 'Testing enum' + required: false +runs: + using: 'docker' + image: 'Dockerfile'