From 933097e01ce2ad52121aa633231d0a569f0cc6b9 Mon Sep 17 00:00:00 2001 From: Matas Lauzadis Date: Tue, 23 Sep 2025 21:08:13 -0400 Subject: [PATCH 1/4] Remove `signing = true` configuration --- .../aws/sdk/kotlin/gradle/dsl/Publish.kt | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/Publish.kt b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/Publish.kt index 30e8e5a..ec6cc14 100644 --- a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/Publish.kt +++ b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/Publish.kt @@ -365,10 +365,21 @@ fun Project.configureJReleaser() { maven { mavenCentral { create("maven-central") { - active = Active.ALWAYS // the Maven deployer default is ALWAYS, but MavenCentral is NEVER - sign = false // Signing is done when publishing, see the 'configurePublishing' function + active = Active.ALWAYS // the MavenDeployer default is ALWAYS, but MavenCentralDeployer is NEVER url = "https://central.sonatype.com/api/v1/publisher" stagingRepository(rootProject.layout.buildDirectory.dir("m2").get().toString()) + + maxRetries = 100 + retryDelay = 60 // seconds + snapshotSupported = false // do not allow publication of snapshot artifacts + applyMavenCentralRules = true + sign = false // Signing is done when publishing, see the 'configurePublishing' function + // all of the following should be enabled by applyMavenCentralRules but set them explicitly to be sure + checksums = true + sourceJar = true + javadocJar = true + verifyPom = true + artifacts { artifactOverride { artifactId = "version-catalog" @@ -395,16 +406,6 @@ fun Project.configureJReleaser() { } } } - maxRetries = 100 - retryDelay = 60 // seconds - snapshotSupported = false // do not allow publication of snapshot artifacts - applyMavenCentralRules = true - // all of the following should be enabled by applyMavenCentralRules but set them explicitly to be sure - sign = true - checksums = true - sourceJar = true - javadocJar = true - verifyPom = true } } } From c48ba1736905ff192bfa245d992c91a62ec8af62 Mon Sep 17 00:00:00 2001 From: Matas Lauzadis Date: Tue, 30 Sep 2025 11:41:28 -0400 Subject: [PATCH 2/4] Add Sonatype Publish Portal API integration --- build-plugins/build-support/build.gradle.kts | 3 + .../aws/sdk/kotlin/gradle/dsl/Publish.kt | 2 + .../publishing/SonatypeCentralPortalClient.kt | 151 ++++++++++++++++++ .../SonatypeCentralPortalPublishTask.kt | 65 ++++++++ ...typeCentralPortalWaitForPublicationTask.kt | 66 ++++++++ gradle/libs.versions.toml | 5 + 6 files changed, 292 insertions(+) create mode 100644 build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalClient.kt create mode 100644 build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalPublishTask.kt create mode 100644 build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalWaitForPublicationTask.kt diff --git a/build-plugins/build-support/build.gradle.kts b/build-plugins/build-support/build.gradle.kts index 2c8de49..9e4b274 100644 --- a/build-plugins/build-support/build.gradle.kts +++ b/build-plugins/build-support/build.gradle.kts @@ -23,6 +23,9 @@ dependencies { compileOnly(gradleApi()) implementation(libs.aws.sdk.s3) implementation(libs.aws.sdk.cloudwatch) + implementation(libs.okhttp) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.coroutines.core) testImplementation(kotlin("test")) testImplementation(libs.kotlinx.coroutines.test) } diff --git a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/Publish.kt b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/Publish.kt index ec6cc14..7dbeb57 100644 --- a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/Publish.kt +++ b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/dsl/Publish.kt @@ -19,6 +19,8 @@ import org.jreleaser.gradle.plugin.JReleaserExtension import org.jreleaser.model.Active import java.time.Duration +// FIXME Relocate this file to `aws.sdk.kotlin.gradle.publishing` + private object Properties { const val SKIP_PUBLISHING = "skipPublish" } diff --git a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalClient.kt b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalClient.kt new file mode 100644 index 0000000..c70c44c --- /dev/null +++ b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalClient.kt @@ -0,0 +1,151 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package aws.sdk.kotlin.gradle.publishing + +import java.io.File +import java.util.Base64 +import java.time.Duration +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlin.time.ExperimentalTime +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.HttpUrl.Companion.toHttpUrl +import kotlin.time.Clock + +/** + * A client used for interacting with the Sonatype Publish Portal API + * https://central.sonatype.org/publish/publish-portal-api/ + */ +class SonatypeCentralPortalClient( + private val authHeader: String, + private val client: OkHttpClient = OkHttpClient.Builder() + .connectTimeout(Duration.ofSeconds(30)) + .readTimeout(Duration.ofSeconds(60)) + .writeTimeout(Duration.ofSeconds(60)) + .retryOnConnectionFailure(true) + .build(), + private val json: Json = Json { ignoreUnknownKeys = true; prettyPrint = true } +) { + companion object { + const val CENTRAL_PORTAL_USERNAME = "SONATYPE_CENTRAL_PORTAL_USERNAME" + const val CENTRAL_PORTAL_PASSWORD = "SONATYPE_CENTRAL_PORTAL_PASSWORD" + const val CENTRAL_PORTAL_BASE_URL = "https://central.sonatype.com" + + fun buildAuthHeader(user: String, password: String): String { + val b64 = Base64.getEncoder().encodeToString("$user:$password".toByteArray(Charsets.UTF_8)) + return "Bearer $b64" + } + + /** Helper to create a client using env vars for creds. */ + fun fromEnvironment(): SonatypeCentralPortalClient { + val user = System.getenv(CENTRAL_PORTAL_USERNAME)?.takeIf { it.isNotBlank() } ?: error("$CENTRAL_PORTAL_USERNAME not configured") + val pass = System.getenv(CENTRAL_PORTAL_PASSWORD)?.takeIf { it.isNotBlank() } ?: error("$CENTRAL_PORTAL_PASSWORD not configured") + return SonatypeCentralPortalClient(buildAuthHeader(user, pass)) + } + } + + private val apiBase = CENTRAL_PORTAL_BASE_URL.toHttpUrl() + + @Serializable + data class StatusResponse( + val deploymentId: String, + val deploymentName: String? = null, + val deploymentState: String, + val purls: List? = null, + val errors: Map>? = null, + ) + + /** Uploads a bundle and returns deploymentId. */ + fun uploadBundle(bundle: File, deploymentName: String): String { + require(bundle.isFile && bundle.length() > 0L) { "Bundle does not exist or is empty: $bundle" } + + val url = apiBase.newBuilder() + .addPathSegments("api/v1/publisher/upload") + .addQueryParameter("name", deploymentName) + .addQueryParameter("publishingType", "AUTOMATIC") // set USER_MANAGED to upload the deployment, but not release it + .build() + + val body = MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart( + "bundle", + bundle.name, + bundle.asRequestBody("application/octet-stream".toMediaType()), + ) + .build() + + val request = Request.Builder() + .url(url) + .header("Authorization", authHeader) + .post(body) + .build() + + client.newCall(request).execute().use { resp -> + if (!resp.isSuccessful) throw httpError("upload", resp) + val id = resp.body?.string()?.trim().orEmpty() + if (resp.code != 201 || id.isEmpty()) { + throw RuntimeException("Upload returned ${resp.code} but no deploymentId body; body=$id") + } + return id + } + } + + /** Returns current deployment status. */ + fun getStatus(deploymentId: String): StatusResponse { + val url = apiBase.newBuilder() + .addPathSegments("api/v1/publisher/status") + .addQueryParameter("id", deploymentId) + .build() + + val request = Request.Builder() + .url(url) + .header("Authorization", authHeader) + .post("".toRequestBody(null)) + .build() + + client.newCall(request).execute().use { resp -> + if (!resp.isSuccessful) throw httpError("status", resp) + val payload = resp.body?.string().orEmpty() + return try { + json.decodeFromString(payload) + } catch (e: Exception) { + throw RuntimeException("Failed to parse status JSON (HTTP ${resp.code}): $payload", e) + } + } + } + + /** Polls until one of [terminalStates] is reached, returning the final StatusResponse. */ + @OptIn(ExperimentalTime::class) // for Clock.System.now() + fun waitForStatus( + deploymentId: String, + terminalStates: Set, + pollInterval: kotlin.time.Duration, + timeout: kotlin.time.Duration, + onStateChange: (old: String?, new: String) -> Unit = { _, _ -> } + ): StatusResponse { + val deadline = Clock.System.now() + timeout + var lastState: String? = null + + while (Clock.System.now() < deadline) { + val status = getStatus(deploymentId) + if (status.deploymentState != lastState) { + onStateChange(lastState, status.deploymentState) + lastState = status.deploymentState + } + if (status.deploymentState in terminalStates) return status + Thread.sleep(pollInterval.inWholeMilliseconds) + } + throw RuntimeException("Timed out waiting for deployment $deploymentId to reach one of $terminalStates") + } + + private fun httpError(context: String, resp: Response): RuntimeException { + val body = resp.body?.string().orEmpty() + return RuntimeException("HTTP error during $context: ${resp.code}.\nResponse: $body") + } +} diff --git a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalPublishTask.kt b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalPublishTask.kt new file mode 100644 index 0000000..f552ee2 --- /dev/null +++ b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalPublishTask.kt @@ -0,0 +1,65 @@ +package aws.sdk.kotlin.gradle.publishing + +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlin.time.Duration.Companion.minutes + +/** + * Publishes a bundle to Sonatype Portal and waits for completion. + * States: PENDING, VALIDATING, VALIDATED, FAILED + * https://central.sonatype.org/publish/publish-portal-api/ + */ +abstract class SonatypeCentralPortalPublishTask : DefaultTask() { + @get:InputFile + abstract val bundle: RegularFileProperty + + /** Max time to wait for final state. */ + @get:Input + @get:Optional + abstract val timeoutDuration: Property + + /** Poll interval. */ + @get:Input + @get:Optional + abstract val pollInterval: Property + + init { + timeoutDuration.convention(45.minutes) + pollInterval.convention(15.seconds) + } + + @TaskAction + fun run() { + val client = SonatypeCentralPortalClient.fromEnvironment() + val file = bundle.asFile.get() + + // 1) Upload + val deploymentId = client.uploadBundle(file, file.name) + logger.lifecycle("📤 Uploaded bundle; deploymentId=$deploymentId") + + // 2) Wait for VALIDATED or FAILED + val result = client.waitForStatus( + deploymentId = deploymentId, + terminalStates = setOf("VALIDATED", "FAILED"), + pollInterval = pollInterval.get(), + timeout = timeoutDuration.get(), + ) { _, new -> + logger.lifecycle("📡 Status: $new (deploymentId=$deploymentId)") + } + + // 3) Evaluate + when (result.deploymentState) { + "VALIDATED" -> logger.lifecycle("✅ Bundle validated by Maven Central") + "FAILED" -> { + val reasons = result.errors?.values?.joinToString("\n- ", prefix = "\n- ") + ?: "\n(no error details returned)" + throw RuntimeException("❌ Sonatype deployment FAILED for ${result.deploymentId}$reasons") + } + else -> error("Unexpected terminal state: ${result.deploymentState}") + } + } +} diff --git a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalWaitForPublicationTask.kt b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalWaitForPublicationTask.kt new file mode 100644 index 0000000..c5b84ea --- /dev/null +++ b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalWaitForPublicationTask.kt @@ -0,0 +1,66 @@ +package aws.sdk.kotlin.gradle.publishing + +import org.gradle.api.DefaultTask +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.options.Option +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds + +/** + * Waits for a given deploymentId to enter the PUBLISHED state + * https://central.sonatype.org/publish/publish-portal-api/ + */ +abstract class SonatypeCentralPortalWaitForPublicationTask: DefaultTask() { + @get:Input + abstract val deploymentId: Property + + @Option(option = "deploymentId", description = "Deployment ID to wait for") + fun setDeploymentId(id: String) { + deploymentId.set(id) + } + + /** Max time to wait for final state. */ + @get:Input + @get:Optional + abstract val timeoutDuration: Property + + /** Poll interval. */ + @get:Input + @get:Optional + abstract val pollInterval: Property + + init { + timeoutDuration.convention(90.minutes) + pollInterval.convention(30.seconds) + } + + @TaskAction + fun run() { + val client = SonatypeCentralPortalClient.fromEnvironment() + val deploymentId = deploymentId.get().takeIf { it.isNotBlank() } ?: error("deploymentId not configured") + + val result = client.waitForStatus( + deploymentId, + setOf("PUBLISHED", "FAILED"), + pollInterval.get(), + timeoutDuration.get() + ) { _, newState -> + logger.lifecycle("📡 Status: $newState (deploymentId=$deploymentId)") + } + + when (result.deploymentState) { + "PUBLISHED" -> { + logger.lifecycle("🚀 Deployment PUBLISHED (deploymentId=$deploymentId)") + } + "FAILED" -> { + val reasons = result.errors?.values?.joinToString("\n- ", prefix = "\n- ") ?: "\n(no error details returned)" + throw RuntimeException("❌ Sonatype publication FAILED for $deploymentId$reasons") + } + else -> error("Unexpected terminal state: ${result.deploymentState}") + } + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c447d2b..8259583 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,8 @@ smithy-gradle-plugin-version = "1.3.0" junit-version = "5.10.1" coroutines-version = "1.10.2" slf4j-version = "2.0.17" +okhttp-version = "5.1.0" +kotlinx-serialization-version = "1.9.0" [libraries] aws-sdk-cloudwatch = { module = "aws.sdk.kotlin:cloudwatch", version.ref = "aws-sdk-version" } @@ -22,8 +24,11 @@ jreleaser-plugin = { module = "org.jreleaser:jreleaser-gradle-plugin", version.r smithy-model = { module = "software.amazon.smithy:smithy-model", version.ref = "smithy-version" } smithy-gradle-base-plugin = { module = "software.amazon.smithy.gradle:smithy-base", version.ref = "smithy-gradle-plugin-version" } junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-version" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines-version" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines-version" } slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j-version" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp-version" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-version" } [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" } From 6f5c2167f7b7031772f773f4f50292c57c0c79e8 Mon Sep 17 00:00:00 2001 From: Matas Lauzadis Date: Tue, 30 Sep 2025 11:43:39 -0400 Subject: [PATCH 3/4] lint --- .../publishing/SonatypeCentralPortalClient.kt | 17 ++++++++++------- .../SonatypeCentralPortalPublishTask.kt | 9 +++++++-- ...natypeCentralPortalWaitForPublicationTask.kt | 11 ++++++++--- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalClient.kt b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalClient.kt index c70c44c..7414a56 100644 --- a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalClient.kt +++ b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalClient.kt @@ -5,18 +5,18 @@ package aws.sdk.kotlin.gradle.publishing -import java.io.File -import java.util.Base64 -import java.time.Duration import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json -import kotlin.time.ExperimentalTime import okhttp3.* +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.HttpUrl.Companion.toHttpUrl +import java.io.File +import java.time.Duration +import java.util.Base64 import kotlin.time.Clock +import kotlin.time.ExperimentalTime /** * A client used for interacting with the Sonatype Publish Portal API @@ -30,7 +30,10 @@ class SonatypeCentralPortalClient( .writeTimeout(Duration.ofSeconds(60)) .retryOnConnectionFailure(true) .build(), - private val json: Json = Json { ignoreUnknownKeys = true; prettyPrint = true } + private val json: Json = Json { + ignoreUnknownKeys = true + prettyPrint = true + }, ) { companion object { const val CENTRAL_PORTAL_USERNAME = "SONATYPE_CENTRAL_PORTAL_USERNAME" @@ -127,7 +130,7 @@ class SonatypeCentralPortalClient( terminalStates: Set, pollInterval: kotlin.time.Duration, timeout: kotlin.time.Duration, - onStateChange: (old: String?, new: String) -> Unit = { _, _ -> } + onStateChange: (old: String?, new: String) -> Unit = { _, _ -> }, ): StatusResponse { val deadline = Clock.System.now() + timeout var lastState: String? = null diff --git a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalPublishTask.kt b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalPublishTask.kt index f552ee2..8ea1c7d 100644 --- a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalPublishTask.kt +++ b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalPublishTask.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.sdk.kotlin.gradle.publishing import org.gradle.api.DefaultTask @@ -5,11 +10,11 @@ import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.* import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.seconds /** - * Publishes a bundle to Sonatype Portal and waits for completion. + * Publishes a bundle to Sonatype Portal and waits for it to be validated (but not fully published). * States: PENDING, VALIDATING, VALIDATED, FAILED * https://central.sonatype.org/publish/publish-portal-api/ */ diff --git a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalWaitForPublicationTask.kt b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalWaitForPublicationTask.kt index c5b84ea..5089f1c 100644 --- a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalWaitForPublicationTask.kt +++ b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalWaitForPublicationTask.kt @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + package aws.sdk.kotlin.gradle.publishing import org.gradle.api.DefaultTask @@ -14,7 +19,7 @@ import kotlin.time.Duration.Companion.seconds * Waits for a given deploymentId to enter the PUBLISHED state * https://central.sonatype.org/publish/publish-portal-api/ */ -abstract class SonatypeCentralPortalWaitForPublicationTask: DefaultTask() { +abstract class SonatypeCentralPortalWaitForPublicationTask : DefaultTask() { @get:Input abstract val deploymentId: Property @@ -47,7 +52,7 @@ abstract class SonatypeCentralPortalWaitForPublicationTask: DefaultTask() { deploymentId, setOf("PUBLISHED", "FAILED"), pollInterval.get(), - timeoutDuration.get() + timeoutDuration.get(), ) { _, newState -> logger.lifecycle("📡 Status: $newState (deploymentId=$deploymentId)") } @@ -63,4 +68,4 @@ abstract class SonatypeCentralPortalWaitForPublicationTask: DefaultTask() { else -> error("Unexpected terminal state: ${result.deploymentState}") } } -} \ No newline at end of file +} From 4f22fbd7080176bf94c0988dd26539aa24814cb7 Mon Sep 17 00:00:00 2001 From: Matas Lauzadis Date: Tue, 30 Sep 2025 11:44:45 -0400 Subject: [PATCH 4/4] remove comment --- .../sdk/kotlin/gradle/publishing/SonatypeCentralPortalClient.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalClient.kt b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalClient.kt index 7414a56..92fe635 100644 --- a/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalClient.kt +++ b/build-plugins/build-support/src/main/kotlin/aws/sdk/kotlin/gradle/publishing/SonatypeCentralPortalClient.kt @@ -45,7 +45,6 @@ class SonatypeCentralPortalClient( return "Bearer $b64" } - /** Helper to create a client using env vars for creds. */ fun fromEnvironment(): SonatypeCentralPortalClient { val user = System.getenv(CENTRAL_PORTAL_USERNAME)?.takeIf { it.isNotBlank() } ?: error("$CENTRAL_PORTAL_USERNAME not configured") val pass = System.getenv(CENTRAL_PORTAL_PASSWORD)?.takeIf { it.isNotBlank() } ?: error("$CENTRAL_PORTAL_PASSWORD not configured")