diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 00000000..9dec65f6 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,50 @@ +name: CD + +on: + push: + tags: + - 'v*' + +permissions: + contents: read + +jobs: + ci: + uses: ./.github/workflows/ci.yml + permissions: + contents: read + pull-requests: write + + validate-tag: + runs-on: ubuntu-latest + steps: + - name: Validate semver tag + run: | + TAG="${GITHUB_REF_NAME}" + if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(.*)$ ]]; then + echo "::error::Tag '$TAG' does not match semver format (expected v..)" + exit 1 + fi + + publish: + needs: [ ci, validate-tag ] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + + - uses: gradle/actions/setup-gradle@v4 + + - name: Publish to Maven Central + env: + RELEASE_VERSION: ${{ github.ref_name }} + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_SIGNING_PASSWORD }} + run: ./gradlew publishAndReleaseToMavenCentral --no-configuration-cache diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cae7c3e..f80c66db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,7 @@ on: branches: [ master ] pull_request: branches: [ master ] + workflow_call: permissions: contents: read diff --git a/README.md b/README.md index 3a764e6c..bf3e9446 100644 --- a/README.md +++ b/README.md @@ -1 +1,92 @@ # justworks + +A Gradle plugin that generates type-safe Kotlin [Ktor](https://ktor.io/) client code from OpenAPI 3.0 specifications. + +## Installation + +Add the plugin to your `build.gradle.kts`: + +```kotlin +plugins { + id("com.avsystem.justworks") version "" +} +``` + +The plugin and core library are published to Maven Central: + +- `com.avsystem.justworks:plugin` -- Gradle plugin +- `com.avsystem.justworks:core` -- OpenAPI parser and code generator + +## Usage + +Configure one or more OpenAPI specs in the `justworks` extension: + +```kotlin +justworks { + specs { + register("petstore") { + specFile = file("api/petstore.yaml") + packageName = "com.example.petstore" + } + register("payments") { + specFile = file("api/payments.yaml") + packageName = "com.example.payments" + // Optional: override default sub-packages + apiPackage = "com.example.payments.client" + modelPackage = "com.example.payments.dto" + } + } +} +``` + +Each spec gets its own Gradle task (`justworksGenerate`) and output directory. Run all generators at once with: + +```bash +./gradlew justworksGenerateAll +``` + +Generated sources are automatically wired into Kotlin source sets, so `compileKotlin` depends on code generation -- no extra configuration needed. + +### Configuration options + +| Property | Required | Default | Description | +|----------------|----------|--------------------------|--------------------------------------| +| `specFile` | Yes | -- | Path to the OpenAPI spec (.yaml/.json) | +| `packageName` | Yes | -- | Base package for generated code | +| `apiPackage` | No | `$packageName.api` | Package for API client classes | +| `modelPackage` | No | `$packageName.model` | Package for model/data classes | + +## Publishing + +Releases are published to [Maven Central](https://central.sonatype.com/) automatically when a version tag (`v*`) is pushed. The CD pipeline runs CI checks first, then publishes signed artifacts via the [vanniktech maven-publish](https://github.com/vanniktech/gradle-maven-publish-plugin) plugin. + +To trigger a release: + +```bash +git tag v1.0.0 +git push origin v1.0.0 +``` + +The version is derived from the tag (stripping the `v` prefix). Without a tag, the version defaults to `0.0.1-SNAPSHOT`. + +## Development + +Requires **JDK 21+**. + +```bash +# Run tests and linting +./gradlew check + +# Run only unit tests +./gradlew test + +# Run functional tests (plugin module) +./gradlew plugin:functionalTest + +# Lint check +./gradlew ktlintCheck +``` + +## License + +Apache License 2.0 diff --git a/build.gradle.kts b/build.gradle.kts index a55997f7..2e9bcb6e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,11 +1,19 @@ +import com.vanniktech.maven.publish.MavenPublishBaseExtension +import com.vanniktech.maven.publish.SonatypeHost + plugins { kotlin("jvm") version "2.3.0" apply false id("org.jlleitschuh.gradle.ktlint") version "12.1.2" apply false + id("org.jetbrains.kotlinx.kover") version "0.9.1" apply false + id("com.vanniktech.maven.publish") version "0.30.0" apply false } allprojects { - group = "com.avsystem" - version = "0.0.1" + group = "com.avsystem.justworks" + version = System + .getenv("RELEASE_VERSION") + ?.let { Regex("""^v(\d+\.\d+\.\d+.*)$""").matchEntire(it)?.groupValues?.get(1) } + ?: "0.0.1-SNAPSHOT" repositories { mavenCentral() @@ -13,3 +21,46 @@ allprojects { apply(plugin = "org.jlleitschuh.gradle.ktlint") } + +subprojects { + pluginManager.withPlugin("com.vanniktech.maven.publish") { + extensions.configure { + publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) + signAllPublications() + + pom { + url = "https://github.com/AVSystem/justworks" + + licenses { + license { + name = "The Apache License, Version 2.0" + url = "https://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + + developers { + developer { + id = "halotukozak" + name = "Bartłomiej Kozak" + email = "b.kozak@avsystem.com" + organization = "AVSystem" + organizationUrl = "https://www.avsystem.com" + } + developer { + id = "mzielu" + name = "Marcin Zieliński" + email = "m.zielinski@avsystem.com" + organization = "AVSystem" + organizationUrl = "https://www.avsystem.com" + } + } + + scm { + connection = "scm:git:git://github.com/AVSystem/justworks.git" + developerConnection = "scm:git:ssh://github.com/AVSystem/justworks.git" + url = "https://github.com/AVSystem/justworks" + } + } + } + } +} diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 1dbfccca..1990a3bc 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -2,8 +2,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("jvm") - `maven-publish` - id("org.jetbrains.kotlinx.kover") version "0.9.1" + id("org.jetbrains.kotlinx.kover") + id("com.vanniktech.maven.publish") } kotlin { @@ -12,9 +12,15 @@ kotlin { // todo: remove when https://github.com/JLLeitschuh/ktlint-gradle/issues/912 resolved ktlint { - version.set("1.8.0") + version = "1.8.0" } +mavenPublishing { + pom { + name = "justworks-core" + description = "OpenAPI 3.0 parser and Kotlin Ktor client code generator" + } +} dependencies { implementation("io.swagger.parser.v3:swagger-parser:2.1.39") @@ -28,13 +34,7 @@ tasks.test { useJUnitPlatform() } -publishing { - publications { - create("maven") { - from(components["java"]) - } - } -} + tasks.withType().configureEach { compilerOptions { freeCompilerArgs.add("-Xcontext-parameters") diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 4826a9a4..6119aac0 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -1,8 +1,11 @@ +import com.vanniktech.maven.publish.GradlePlugin +import com.vanniktech.maven.publish.JavadocJar + plugins { kotlin("jvm") `java-gradle-plugin` - `maven-publish` - id("org.jetbrains.kotlinx.kover") version "0.9.1" + id("com.vanniktech.maven.publish") + id("org.jetbrains.kotlinx.kover") } kotlin { @@ -23,6 +26,15 @@ gradlePlugin { } } +mavenPublishing { + configure(GradlePlugin(javadocJar = JavadocJar.Empty())) + + pom { + name = "justworks-plugin" + description = "Gradle plugin for generating Kotlin Ktor client code from OpenAPI 3.0 specifications" + } +} + // Functional test source set val functionalTest by sourceSets.creating { compileClasspath += sourceSets["main"].output