From aed7dc54bc30c8bdcc82c3188abb22307313392d Mon Sep 17 00:00:00 2001 From: soamdesai-tfh Date: Mon, 15 Jun 2026 10:55:58 -0700 Subject: [PATCH 1/5] feat(kotlin): publish to Maven Central from the release workflow Build on the Central-ready Kotlin publication now on main by wiring Maven Central into the release pipeline. The new step passes -Pidkit.publish.mavenCentral=true to activate the Central repository and signing, and runs before the GitHub Packages step. Dev releases omit the flag and publish to GitHub Packages alone. - publish-kotlin.yml: add a production-gated "Publish to Maven Central" step - README: lead with Maven Central as the no-auth install path; note the release workflow now publishes to both Central and GitHub Packages - scripts/publish-relocation-pom.sh: one-time relocation POM pointing the old com.worldcoin:idkit-kotlin coordinates at com.worldcoin:idkit Co-Authored-By: Claude Fable 5 --- .github/workflows/publish-kotlin.yml | 22 ++++- kotlin/README.md | 5 +- scripts/publish-relocation-pom.sh | 127 +++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 4 deletions(-) create mode 100755 scripts/publish-relocation-pom.sh diff --git a/.github/workflows/publish-kotlin.yml b/.github/workflows/publish-kotlin.yml index ae6610f2..3098b97f 100644 --- a/.github/workflows/publish-kotlin.yml +++ b/.github/workflows/publish-kotlin.yml @@ -250,7 +250,7 @@ jobs: retention-days: 1 publish: - name: Publish to GitHub Packages + name: Publish runs-on: ubuntu-latest needs: [prepare, build-host, build-android] environment: ${{ needs.prepare.outputs.environment }} @@ -324,6 +324,26 @@ jobs: env: PKG_VERSION: ${{ needs.prepare.outputs.version }} + # Production only. The -Pidkit.publish.mavenCentral=true flag is what registers + # the Maven Central repository and signing tasks; without it (every dev release) + # the step below publishes to GitHub Packages alone. Runs before GitHub Packages + # so a Central validation failure aborts the release before anything is published + # (Central releases are immutable; GitHub Packages versions are recoverable). + # USER_MANAGED for the first release — confirm the deployment in the Central + # Portal UI. TODO: switch to publishAndReleaseToMavenCentral once validated once. + - name: Publish to Maven Central + if: needs.prepare.outputs.environment == 'production' + uses: gradle/gradle-build-action@v3 + with: + gradle-version: 8.9 + arguments: -p kotlin bindings:publishToMavenCentral -Pidkit.publish.mavenCentral=true + env: + PKG_VERSION: ${{ needs.prepare.outputs.version }} + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_SIGNING_KEY_PASSWORD }} + - name: Publish to GitHub Packages uses: gradle/gradle-build-action@v3 with: diff --git a/kotlin/README.md b/kotlin/README.md index 36799fb5..e6693f5c 100644 --- a/kotlin/README.md +++ b/kotlin/README.md @@ -4,8 +4,7 @@ Kotlin SDK for World ID verification, backed by the Rust core via UniFFI. ## Installation -The Kotlin package is published to GitHub Packages as `com.worldcoin:idkit`. -The same publication is also prepared for `mavenLocal()` and Maven Central. Maven Central upload is available as an explicit local opt-in, but it is intentionally not wired into GitHub release workflows until the required secrets are available there and the workflow has been tested end to end. +The Kotlin SDK is published to Maven Central as `com.worldcoin:idkit` — add `mavenCentral()` to your repositories and depend on it with no authentication. Release builds are also published to GitHub Packages; dev builds (`X.Y.Z-dev.`) are published there only. GitHub Packages requires authentication for Maven downloads, even for public packages. Create a token with `read:packages` and expose it through environment variables. @@ -215,7 +214,7 @@ gradle -p kotlin bindings:test ## Publishing -The existing Kotlin release workflow publishes to GitHub Packages. That path is still active and uses GitHub's package credentials: +On production releases the Kotlin release workflow publishes to both Maven Central and GitHub Packages. The GitHub Packages path uses GitHub's package credentials and can also be run locally: ```bash ./kotlin/Examples/IDKitSampleApp/gradlew -p kotlin :bindings:publish diff --git a/scripts/publish-relocation-pom.sh b/scripts/publish-relocation-pom.sh new file mode 100755 index 00000000..221524d7 --- /dev/null +++ b/scripts/publish-relocation-pom.sh @@ -0,0 +1,127 @@ +#!/bin/bash +set -euo pipefail + +# One-time script: publish a relocation POM for the superseded com.worldcoin:idkit-kotlin +# artifact (last real release: 3.1.0, from the archived worldcoin/idkit-kotlin repo), +# pointing consumers at the renamed com.worldcoin:idkit artifact in this monorepo. +# +# Maven resolves automatically; for Gradle users the POM mainly serves as +# discoverable documentation of the rename on central.sonatype.com / mvnrepository.com. +# +# Run AFTER the first com.worldcoin:idkit release is live on Maven Central, so the +# relocation target exists. +# +# Requirements: +# - gpg with the release signing key imported (same key used by CI for Central) +# - Central Portal user token with access to the com.worldcoin namespace +# +# Usage: +# MAVEN_CENTRAL_USERNAME=... MAVEN_CENTRAL_PASSWORD=... \ +# scripts/publish-relocation-pom.sh [signing-key-id] +# +# the com.worldcoin:idkit version on Central to relocate to (e.g. 4.1.0) +# [signing-key-id] optional gpg key selector; defaults to gpg's default key +# +# If MAVEN_CENTRAL_USERNAME/PASSWORD are unset, the script still produces the bundle zip +# and prints instructions for manual upload in the Central Portal UI. + +TARGET_VERSION="${1:?usage: publish-relocation-pom.sh [signing-key-id]}" +SIGNING_KEY_ID="${2:-}" + +# Must sort above 3.1.0 so resolvers treat the relocation POM as the latest version +# of the old coordinates. +RELOCATION_VERSION="3.2.0" +GROUP_ID="com.worldcoin" +OLD_ARTIFACT_ID="idkit-kotlin" +NEW_ARTIFACT_ID="idkit" + +WORK_DIR="$(mktemp -d)" +trap 'rm -rf "$WORK_DIR"' EXIT + +ARTIFACT_DIR="$WORK_DIR/com/worldcoin/$OLD_ARTIFACT_ID/$RELOCATION_VERSION" +POM_FILE="$ARTIFACT_DIR/$OLD_ARTIFACT_ID-$RELOCATION_VERSION.pom" +BUNDLE="$PWD/$OLD_ARTIFACT_ID-$RELOCATION_VERSION-relocation-bundle.zip" + +mkdir -p "$ARTIFACT_DIR" + +echo "📝 Generating relocation POM ($GROUP_ID:$OLD_ARTIFACT_ID:$RELOCATION_VERSION -> $GROUP_ID:$NEW_ARTIFACT_ID:$TARGET_VERSION)" + +cat > "$POM_FILE" < + + 4.0.0 + $GROUP_ID + $OLD_ARTIFACT_ID + $RELOCATION_VERSION + pom + IDKit (Kotlin) — relocated + This artifact has been renamed. Use com.worldcoin:idkit, published from the worldcoin/idkit monorepo. + https://github.com/worldcoin/idkit + + + MIT License + https://opensource.org/licenses/MIT + + + + + worldcoin + Worldcoin + + + + scm:git:https://github.com/worldcoin/idkit.git + scm:git:ssh://git@github.com/worldcoin/idkit.git + https://github.com/worldcoin/idkit + + + + $GROUP_ID + $NEW_ARTIFACT_ID + $TARGET_VERSION + idkit-kotlin moved to the worldcoin/idkit monorepo and was renamed to com.worldcoin:idkit. + + + +EOF + +echo "🔏 Signing POM" +GPG_ARGS=(--armor --detach-sign --output "$POM_FILE.asc") +if [ -n "$SIGNING_KEY_ID" ]; then + GPG_ARGS=(--local-user "$SIGNING_KEY_ID" "${GPG_ARGS[@]}") +fi +gpg "${GPG_ARGS[@]}" "$POM_FILE" + +echo "🧮 Writing checksums" +# Signature files (.asc) are exempt from the checksum requirement — only the POM needs them. +if command -v md5sum >/dev/null; then + md5sum "$POM_FILE" | cut -d' ' -f1 > "$POM_FILE.md5" + sha1sum "$POM_FILE" | cut -d' ' -f1 > "$POM_FILE.sha1" +else + md5 -q "$POM_FILE" > "$POM_FILE.md5" # macOS + shasum -a 1 "$POM_FILE" | cut -d' ' -f1 > "$POM_FILE.sha1" +fi + +echo "đŸ“Ļ Creating bundle" +rm -f "$BUNDLE" +(cd "$WORK_DIR" && zip -qr "$BUNDLE" com) +echo " $BUNDLE" + +if [ -n "${MAVEN_CENTRAL_USERNAME:-}" ] && [ -n "${MAVEN_CENTRAL_PASSWORD:-}" ]; then + echo "🚀 Uploading to Central Portal (USER_MANAGED — release it in the Portal UI)" + # tr guards against GNU base64's 76-char line wrapping on Linux + TOKEN=$(printf '%s:%s' "$MAVEN_CENTRAL_USERNAME" "$MAVEN_CENTRAL_PASSWORD" | base64 | tr -d '\n') + DEPLOYMENT_ID=$(curl --fail --silent --show-error \ + --request POST \ + --header "Authorization: Bearer $TOKEN" \ + --form "bundle=@$BUNDLE" \ + "https://central.sonatype.com/api/v1/publisher/upload?name=$OLD_ARTIFACT_ID-$RELOCATION_VERSION-relocation&publishingType=USER_MANAGED") + echo " Deployment ID: $DEPLOYMENT_ID" + echo " Review and publish at: https://central.sonatype.com/publishing/deployments" +else + echo "â„šī¸ MAVEN_CENTRAL_USERNAME/PASSWORD not set — skipping upload." + echo " Upload the bundle manually: central.sonatype.com -> Publish -> Upload Component," + echo " or re-run with credentials set." +fi From a8ac17336723229e7377c24acc0854ece3b87a24 Mon Sep 17 00:00:00 2001 From: soamdesai-tfh Date: Mon, 15 Jun 2026 13:02:34 -0700 Subject: [PATCH 2/5] docs(kotlin): fix stale Publishing paragraph The closing paragraph still said the release workflow does not publish to Maven Central, contradicting the section intro now that this PR wires it in. Rewrite it to describe the automated production Central publish. Co-Authored-By: Claude Fable 5 --- kotlin/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlin/README.md b/kotlin/README.md index e6693f5c..6961c0e4 100644 --- a/kotlin/README.md +++ b/kotlin/README.md @@ -254,7 +254,7 @@ To upload and release from the Central Portal deployment in one command, run: :bindings:publishAndReleaseToMavenCentral ``` -The automated release workflow continues publishing Kotlin artifacts to GitHub Packages, but it does not publish to Maven Central yet. Add Maven Central release-workflow steps only after the required credentials and end-to-end release path are ready. +On production releases the workflow runs this Central publish step automatically, using the Sonatype and GPG signing credentials stored as `production` environment secrets. The first release uploads to the Central Portal for manual confirmation before going live; a follow-up change switches it to fully automatic. ## Troubleshooting From 2452eed6413e7f2196ecd4f8a9d2ee8b43a7cea7 Mon Sep 17 00:00:00 2001 From: soamdesai-tfh Date: Mon, 15 Jun 2026 13:25:46 -0700 Subject: [PATCH 3/5] docs(ci): correct Maven Central step comment on validation timing The comment claimed Central-first ordering aborts on validation failure before GitHub Packages publishes. In vanniktech 0.34.0 publishToMavenCentral only uploads the deployment and returns; Portal validation is asynchronous and is not awaited in CI, so it cannot fail the job. Clarify that only build/signing/upload failures abort, and that USER_MANAGED manual confirm in the Portal is the real gate. Co-Authored-By: Claude Fable 5 --- .github/workflows/publish-kotlin.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish-kotlin.yml b/.github/workflows/publish-kotlin.yml index 3098b97f..fad419eb 100644 --- a/.github/workflows/publish-kotlin.yml +++ b/.github/workflows/publish-kotlin.yml @@ -324,13 +324,18 @@ jobs: env: PKG_VERSION: ${{ needs.prepare.outputs.version }} - # Production only. The -Pidkit.publish.mavenCentral=true flag is what registers - # the Maven Central repository and signing tasks; without it (every dev release) - # the step below publishes to GitHub Packages alone. Runs before GitHub Packages - # so a Central validation failure aborts the release before anything is published - # (Central releases are immutable; GitHub Packages versions are recoverable). - # USER_MANAGED for the first release — confirm the deployment in the Central - # Portal UI. TODO: switch to publishAndReleaseToMavenCentral once validated once. + # Production only. The -Pidkit.publish.mavenCentral=true flag registers the + # Maven Central repository and signing tasks; without it (every dev release) the + # step below publishes to GitHub Packages alone. + # + # Runs before GitHub Packages so a build/signing/upload failure (which fails the + # Gradle task) aborts before anything is published. Note: this task only uploads + # the deployment — Central Portal validation is asynchronous and is NOT awaited + # here, so a validation failure surfaces later in the Portal, not as a failed job. + # USER_MANAGED leaves the deployment awaiting a manual Publish in the Portal UI, + # so that review is the real gate on what reaches Maven Central. (Switching to + # publishAndReleaseToMavenCentral only changes server-side auto-release; it also + # returns right after upload and does not wait for validation in CI.) - name: Publish to Maven Central if: needs.prepare.outputs.environment == 'production' uses: gradle/gradle-build-action@v3 From b7aa8b4867be1b6e71f5a30b20bc33a31764d495 Mon Sep 17 00:00:00 2001 From: soamdesai-tfh Date: Mon, 15 Jun 2026 13:41:59 -0700 Subject: [PATCH 4/5] docs(kotlin): clarify Central availability timing and CI publish task - Installation: note Maven Central availability follows the first release, rather than implying the artifact is already there - Publishing: name the upload-only :bindings:publishToMavenCentral task the workflow runs, so it isn't read as the publishAndReleaseToMavenCentral command shown just above Co-Authored-By: Claude Fable 5 --- kotlin/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlin/README.md b/kotlin/README.md index 6961c0e4..13f15782 100644 --- a/kotlin/README.md +++ b/kotlin/README.md @@ -4,7 +4,7 @@ Kotlin SDK for World ID verification, backed by the Rust core via UniFFI. ## Installation -The Kotlin SDK is published to Maven Central as `com.worldcoin:idkit` — add `mavenCentral()` to your repositories and depend on it with no authentication. Release builds are also published to GitHub Packages; dev builds (`X.Y.Z-dev.`) are published there only. +The Kotlin SDK is published to Maven Central as `com.worldcoin:idkit` — once a version is released there, add `mavenCentral()` to your repositories and depend on it with no authentication. Release builds are also published to GitHub Packages; dev builds (`X.Y.Z-dev.`) are published there only. GitHub Packages requires authentication for Maven downloads, even for public packages. Create a token with `read:packages` and expose it through environment variables. @@ -254,7 +254,7 @@ To upload and release from the Central Portal deployment in one command, run: :bindings:publishAndReleaseToMavenCentral ``` -On production releases the workflow runs this Central publish step automatically, using the Sonatype and GPG signing credentials stored as `production` environment secrets. The first release uploads to the Central Portal for manual confirmation before going live; a follow-up change switches it to fully automatic. +On production releases the workflow runs the upload-only `:bindings:publishToMavenCentral` step automatically (not `publishAndReleaseToMavenCentral`), using the Sonatype and GPG signing credentials stored as `production` environment secrets. The first release uploads to the Central Portal for manual confirmation before going live; a follow-up change switches it to fully automatic. ## Troubleshooting From 6fa5fa9fb4d052823791bf19dc0da808b35ed5a0 Mon Sep 17 00:00:00 2001 From: soamdesai-tfh Date: Mon, 15 Jun 2026 13:56:55 -0700 Subject: [PATCH 5/5] feat(ci): gate GitHub Packages on Maven Central validation publishToMavenCentral only uploads the deployment and returns; Central Portal validation is asynchronous, so GitHub Packages could previously publish even if validation later failed. Add a step that polls the Portal until the deployment is validated before publishing to GitHub Packages, so a validation failure stops the job first. Co-Authored-By: Claude Fable 5 --- .github/workflows/publish-kotlin.yml | 38 ++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/.github/workflows/publish-kotlin.yml b/.github/workflows/publish-kotlin.yml index fad419eb..4faf1ece 100644 --- a/.github/workflows/publish-kotlin.yml +++ b/.github/workflows/publish-kotlin.yml @@ -328,14 +328,11 @@ jobs: # Maven Central repository and signing tasks; without it (every dev release) the # step below publishes to GitHub Packages alone. # - # Runs before GitHub Packages so a build/signing/upload failure (which fails the - # Gradle task) aborts before anything is published. Note: this task only uploads - # the deployment — Central Portal validation is asynchronous and is NOT awaited - # here, so a validation failure surfaces later in the Portal, not as a failed job. - # USER_MANAGED leaves the deployment awaiting a manual Publish in the Portal UI, - # so that review is the real gate on what reaches Maven Central. (Switching to - # publishAndReleaseToMavenCentral only changes server-side auto-release; it also - # returns right after upload and does not wait for validation in CI.) + # This task only UPLOADS the deployment to the Central Portal and returns; Portal + # validation is asynchronous. The next step polls until the deployment validates, + # so GitHub Packages does not publish if Central validation fails. USER_MANAGED + # then leaves the validated deployment awaiting a manual Publish in the Portal UI, + # the final gate on what actually goes live on Maven Central. - name: Publish to Maven Central if: needs.prepare.outputs.environment == 'production' uses: gradle/gradle-build-action@v3 @@ -349,6 +346,31 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_SIGNING_KEY }} ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_SIGNING_KEY_PASSWORD }} + # Gate GitHub Packages on Central validation. The upload above returns before the + # Portal finishes its asynchronous validation, so without this a validation failure + # would still let GitHub Packages publish. Poll the "download from a validated + # deployment" endpoint, which returns 200 only once the deployment is VALIDATED. + - name: Wait for Maven Central validation + if: needs.prepare.outputs.environment == 'production' + env: + MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + PKG_VERSION: ${{ needs.prepare.outputs.version }} + run: | + TOKEN=$(printf '%s:%s' "$MAVEN_CENTRAL_USERNAME" "$MAVEN_CENTRAL_PASSWORD" | base64 | tr -d '\n') + URL="https://central.sonatype.com/api/v1/publisher/deployments/download/com/worldcoin/idkit/${PKG_VERSION}/idkit-${PKG_VERSION}.pom" + for i in $(seq 1 40); do + CODE=$(curl -s -o /dev/null -w '%{http_code}' -H "Authorization: Bearer $TOKEN" "$URL") + if [ "$CODE" = "200" ]; then + echo "Maven Central deployment for $PKG_VERSION validated." + exit 0 + fi + echo "Not validated yet (HTTP $CODE); attempt $i/40, retrying in 15s..." + sleep 15 + done + echo "::error::Maven Central deployment for $PKG_VERSION did not validate within ~10 minutes; skipping GitHub Packages. Check the Central Portal." + exit 1 + - name: Publish to GitHub Packages uses: gradle/gradle-build-action@v3 with: