Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion .github/workflows/publish-kotlin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -324,6 +324,56 @@ jobs:
env:
PKG_VERSION: ${{ needs.prepare.outputs.version }}

# 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.
#
# 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
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 }}

# 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 120); do
# `|| echo 000` keeps a transient curl transport error (DNS/connection/
# timeout) from tripping the runner's `set -e` and aborting the release;
# 000 is not 200, so the loop simply retries.
CODE=$(curl -s -o /dev/null -w '%{http_code}' -H "Authorization: Bearer $TOKEN" "$URL" || echo 000)
if [ "$CODE" = "200" ]; then
echo "Maven Central deployment for $PKG_VERSION validated."
exit 0
fi
echo "Not validated yet (HTTP $CODE); attempt $i/120, retrying in 15s..."
sleep 15
done
echo "::error::Maven Central deployment for $PKG_VERSION did not validate within ~30 minutes; skipping GitHub Packages. Check the Central Portal."
exit 1

- name: Publish to GitHub Packages
uses: gradle/gradle-build-action@v3
with:
Expand Down
14 changes: 4 additions & 10 deletions kotlin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` — 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.<sha>`) 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.
Expand Down Expand Up @@ -210,24 +209,19 @@ If Gradle is available locally:

```bash
gradle -p kotlin bindings:test
./kotlin/Examples/IDKitSampleApp/gradlew -p kotlin :bindings:publishToMavenLocal
```

## 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 GitHub Packages and uploads a signed artifact to Maven Central (the first release awaits manual confirmation in the Central Portal before going live — see below). The GitHub Packages path uses GitHub's package credentials and can also be run locally:

```bash
./kotlin/Examples/IDKitSampleApp/gradlew -p kotlin :bindings:publish
```

Without `-Pidkit.publish.mavenCentral=true`, this does not configure Maven Central upload or signing tasks.

For local integration testing, publish the same artifact to the local Maven repository:

```bash
./kotlin/Examples/IDKitSampleApp/gradlew -p kotlin :bindings:publishToMavenLocal
```
For local integration testing, publish to the local Maven repository with `:bindings:publishToMavenLocal` as described under [Installation](#installation).

To publish to Maven Central from a local machine that already has credentials, keep the secrets in `~/.gradle/gradle.properties`:

Expand Down Expand Up @@ -255,7 +249,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 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

Expand Down
127 changes: 127 additions & 0 deletions scripts/publish-relocation-pom.sh
Original file line number Diff line number Diff line change
@@ -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 <relocation> 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 <target-version> [signing-key-id]
#
# <target-version> 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 <target-version> [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" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>$GROUP_ID</groupId>
<artifactId>$OLD_ARTIFACT_ID</artifactId>
<version>$RELOCATION_VERSION</version>
<packaging>pom</packaging>
<name>IDKit (Kotlin) — relocated</name>
<description>This artifact has been renamed. Use com.worldcoin:idkit, published from the worldcoin/idkit monorepo.</description>
<url>https://github.com/worldcoin/idkit</url>
<licenses>
<license>
<name>MIT License</name>
<url>https://opensource.org/licenses/MIT</url>
</license>
</licenses>
<developers>
<developer>
<id>worldcoin</id>
<name>Worldcoin</name>
</developer>
</developers>
<scm>
<connection>scm:git:https://github.com/worldcoin/idkit.git</connection>
<developerConnection>scm:git:ssh://git@github.com/worldcoin/idkit.git</developerConnection>
<url>https://github.com/worldcoin/idkit</url>
</scm>
<distributionManagement>
<relocation>
<groupId>$GROUP_ID</groupId>
<artifactId>$NEW_ARTIFACT_ID</artifactId>
<version>$TARGET_VERSION</version>
<message>idkit-kotlin moved to the worldcoin/idkit monorepo and was renamed to com.worldcoin:idkit.</message>
</relocation>
</distributionManagement>
</project>
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
Loading