Skip to content

Central Portal Migration #865

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
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
128 changes: 128 additions & 0 deletions .github/workflows/publish-maven.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: Publish to Maven Central
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has not yet been tested. We will still release via the release.sh script for now, but this is a start.


on:
workflow_dispatch:
inputs:
publishing_type:
description: 'Publishing type for the deployment'
required: true
default: 'user_managed'
type: choice
options:
- user_managed
- automatic
version_override:
description: 'Version to publish (leave empty to use gradle.properties VERSION_NAME)'
required: false
type: string

permissions:
contents: read

jobs:
publish:
name: Publish to Maven Central Portal
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Setup Gradle
uses: gradle/gradle-build-action@v2

- name: Configure signing
env:
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
run: |
echo "signing.keyId=${{ secrets.SIGNING_KEY_ID }}" >> ~/.gradle/gradle.properties
echo "signing.password=$SIGNING_PASSWORD" >> ~/.gradle/gradle.properties
echo "signing.secretKeyRingFile=$HOME/.gnupg/secring.gpg" >> ~/.gradle/gradle.properties
mkdir -p ~/.gnupg
echo "$SIGNING_KEY" | base64 --decode > ~/.gnupg/secring.gpg

- name: Configure Portal credentials
env:
CENTRAL_PORTAL_TOKEN: ${{ secrets.CENTRAL_PORTAL_TOKEN }}
CENTRAL_PORTAL_PASSWORD: ${{ secrets.CENTRAL_PORTAL_PASSWORD }}
run: |
echo "centralPortalToken=$CENTRAL_PORTAL_TOKEN" >> ~/.gradle/gradle.properties
echo "centralPortalPassword=$CENTRAL_PORTAL_PASSWORD" >> ~/.gradle/gradle.properties

- name: Override version if specified
if: inputs.version_override != ''
run: |
sed -i "s/VERSION_NAME=.*/VERSION_NAME=${{ inputs.version_override }}/" gradle.properties
echo "Updated version to: ${{ inputs.version_override }}"

- name: Build artifacts
run: |
./gradlew clean build
./gradlew androidJavadocsJar androidSourcesJar

- name: Publish to Maven Central Portal
run: ./gradlew publishRelease

- name: Upload to Maven Central Portal
env:
CENTRAL_PORTAL_TOKEN: ${{ secrets.CENTRAL_PORTAL_TOKEN }}
CENTRAL_PORTAL_PASSWORD: ${{ secrets.CENTRAL_PORTAL_PASSWORD }}
run: |
# Get the namespace from gradle.properties
NAMESPACE=$(grep "^GROUP=" gradle.properties | cut -d'=' -f2)

# Create Bearer auth token (as per docs)
AUTH_TOKEN=$(echo -n "$CENTRAL_PORTAL_TOKEN:$CENTRAL_PORTAL_PASSWORD" | base64)

# Call the manual upload endpoint
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $AUTH_TOKEN" \
"https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/${NAMESPACE}?publishing_type=${{ inputs.publishing_type }}")

HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | sed '$d')

if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ] && [ "$HTTP_CODE" != "204" ]; then
echo "Error: Portal upload failed with HTTP $HTTP_CODE"
echo "Response: $BODY"
exit 1
fi

echo "Success! Deployment uploaded to Portal."

- name: Summary
run: |
VERSION=$(grep "^VERSION_NAME=" gradle.properties | cut -d'=' -f2)
echo "## Publishing Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Version**: $VERSION" >> $GITHUB_STEP_SUMMARY
echo "- **Publishing Type**: ${{ inputs.publishing_type }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ inputs.publishing_type }}" == "user_managed" ]; then
echo "### Next Steps" >> $GITHUB_STEP_SUMMARY
echo "1. Go to [Maven Central Portal Deployments](https://central.sonatype.com/publishing/deployments)" >> $GITHUB_STEP_SUMMARY
echo "2. Find your deployment (version $VERSION)" >> $GITHUB_STEP_SUMMARY
echo "3. Review and click 'Publish' to release to Maven Central" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "⏱️ **Timeline**:" >> $GITHUB_STEP_SUMMARY
echo "- Validation: 15-30 minutes" >> $GITHUB_STEP_SUMMARY
echo "- Portal search: 1-2 hours" >> $GITHUB_STEP_SUMMARY
echo "- Maven Central sync: 2-4 hours" >> $GITHUB_STEP_SUMMARY
else
echo "### Status" >> $GITHUB_STEP_SUMMARY
echo "✅ Deployment will be automatically released if validation passes" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "⏱️ **Timeline**:" >> $GITHUB_STEP_SUMMARY
echo "- Validation and release: 15-30 minutes" >> $GITHUB_STEP_SUMMARY
echo "- Portal search: 1-2 hours" >> $GITHUB_STEP_SUMMARY
echo "- Maven Central sync: 2-4 hours" >> $GITHUB_STEP_SUMMARY
fi
6 changes: 5 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,8 @@ jobs:
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: ${{ steps.generate-release-changelog.outputs.changelog }}
body: |
${{ steps.generate-release-changelog.outputs.changelog }}

---
**Note**: After this GitHub release is created, the Maven Central deployment must be manually released at https://central.sonatype.com/publishing/deployments
63 changes: 59 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,62 @@ The project uses semantic versioning (X.Y.Z) and publishes to Maven Central:
- Release script: `./release.sh [version]`
- Published as: `com.mixpanel.android:mixpanel-android:X.Y.Z`

### Automated Release Process
The `release.sh` script handles the complete release workflow:
1. Updates version in gradle.properties and README.md
2. Builds and publishes artifacts to OSSRH staging
3. Automatically uploads to Maven Central Portal (requires env vars)
4. Creates git tag and updates documentation
5. Updates to next snapshot version

**Required Environment Variables**:
```bash
export CENTRAL_PORTAL_TOKEN=<your-portal-username>
export CENTRAL_PORTAL_PASSWORD=<your-portal-password>
```

### Maven Central Portal Setup

The SDK publishes via the new Maven Central Portal:

1. **Generate Portal Tokens**:
- Log in to https://central.sonatype.com with your OSSRH credentials
- Navigate to your account settings
- Generate a user token (username and password pair)
- Store these securely in `~/.gradle/gradle.properties`:
```
centralPortalToken=<your-token-username>
centralPortalPassword=<your-token-password>
```
- **Security Note**: For enhanced security, consider using encrypted storage options instead of plain text:
- Environment variables: `export CENTRAL_PORTAL_TOKEN=...`
- gradle-credentials-plugin for encrypted storage
- System keychain integration (e.g., macOS Keychain, Windows Credential Store)
- CI/CD secret management systems

2. **Publishing Process**:
- Artifacts are uploaded to OSSRH staging API: `https://ossrh-staging-api.central.sonatype.com/`
- The Portal upload is triggered via the manual API endpoint
- Deployments appear at https://central.sonatype.com/publishing/deployments
- Manual release to Maven Central is required from the Portal UI (unless using automatic publishing)

3. **GitHub Actions**:
- Use the `publish-maven.yml` workflow for automated publishing
- Portal tokens should be stored as repository secrets:
- `CENTRAL_PORTAL_TOKEN` (the token username)
- `CENTRAL_PORTAL_PASSWORD` (the token password)
- Publishing types available:
- `user_managed`: Manual release from Portal UI (default)
- `automatic`: Auto-release if validation passes

4. **Manual Portal Upload** (if automation fails):
```bash
AUTH_TOKEN=$(echo -n "username:password" | base64)
curl -X POST \
-H "Authorization: Bearer $AUTH_TOKEN" \
"https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/com.mixpanel.android?publishing_type=user_managed"
```

## Project Configuration

- Min SDK: 21
Expand Down Expand Up @@ -141,7 +197,6 @@ The project uses semantic versioning (X.Y.Z) and publishes to Maven Central:
- Prepared statements for performance
- Automatic cleanup based on data age

For detailed patterns and examples, see:
- `.claude/context/discovered-patterns.md`
- `.claude/context/architecture/system-design.md`
- `.claude/context/workflows/`
## Memories

- Ensured CLAUDE.md accurately reflects the most recent changes
6 changes: 3 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION_NAME=8.2.0
VERSION_NAME=8.2.1-SNAPSHOT

POM_PACKAGING=aar
GROUP=com.mixpanel.android
Expand All @@ -15,8 +15,8 @@ POM_DEVELOPER_ID=mixpanel_dev
POM_DEVELOPER_NAME=Mixpanel Developers
POM_DEVELOPER_EMAIL=dev+android@mixpanel.com

RELEASE_REPOSITORY_URL=https://oss.sonatype.org/service/local/staging/deploy/maven2/
SNAPSHOT_REPOSITORY_URL=https://oss.sonatype.org/content/repositories/snapshots/
RELEASE_REPOSITORY_URL=https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/
SNAPSHOT_REPOSITORY_URL=https://central.sonatype.com/repository/maven-snapshots/

org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
Expand Down
20 changes: 12 additions & 8 deletions maven.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,9 @@ publishing {

repositories {
maven {
url = uri(RELEASE_REPOSITORY_URL)
credentials {
username = getRepositoryUsername()
password = getRepositoryPassword()
}
}
maven {
url = uri(SNAPSHOT_REPOSITORY_URL)
def releasesRepoUrl = uri(RELEASE_REPOSITORY_URL)
def snapshotsRepoUrl = uri(SNAPSHOT_REPOSITORY_URL)
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
credentials {
username = getRepositoryUsername()
password = getRepositoryPassword()
Expand All @@ -140,6 +135,7 @@ publishing {
}

signing {
required { !version.endsWith("SNAPSHOT") && gradle.taskGraph.hasTask("publishReleasePublicationToMavenRepository") }
sign publishing.publications.release
}

Expand Down Expand Up @@ -201,9 +197,17 @@ tasks.named('signReleasePublication') {
}

def getRepositoryUsername() {
// Support both old OSSRH credentials and new Portal tokens
if (hasProperty('centralPortalToken')) {
return centralPortalToken
}
return hasProperty('sonatypeUsername') ? sonatypeUsername : ""
}

def getRepositoryPassword() {
// Support both old OSSRH credentials and new Portal tokens
if (hasProperty('centralPortalPassword')) {
return centralPortalPassword
}
return hasProperty('sonatypePassword') ? sonatypePassword : ""
}
43 changes: 40 additions & 3 deletions release.sh
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,39 @@ if ! ./gradlew publishRelease ; then
abort
fi

# Upload to Maven Central Portal
printf "\n${YELLOW}Uploading to Maven Central Portal...${NC}\n"

# Check for Portal credentials
if [ -z "$CENTRAL_PORTAL_TOKEN" ] || [ -z "$CENTRAL_PORTAL_PASSWORD" ]; then
printf "${RED}Error: CENTRAL_PORTAL_TOKEN and CENTRAL_PORTAL_PASSWORD environment variables must be set${NC}\n"
printf "${ORANGE}Please set these variables and run the manual upload command:\n"
printf "curl -X POST -H \"Authorization: Bearer \$(echo -n \"TOKEN:PASSWORD\" | base64)\" \\\n"
printf " \"https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/com.mixpanel.android?publishing_type=user_managed\"${NC}\n\n"
abort
fi

# Create auth token
AUTH_TOKEN=$(echo -n "$CENTRAL_PORTAL_TOKEN:$CENTRAL_PORTAL_PASSWORD" | base64)

# Upload to Portal
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $AUTH_TOKEN" \
"https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/com.mixpanel.android?publishing_type=user_managed")

HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | sed '$d')

if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ] && [ "$HTTP_CODE" != "204" ]; then
printf "${RED}Error: Portal upload failed with HTTP $HTTP_CODE${NC}\n"
printf "${ORANGE}Response: $BODY${NC}\n"
printf "\n${ORANGE}The artifacts were published to staging, but not uploaded to the Portal.\n"
printf "You can manually upload using the command above.${NC}\n\n"
abort
fi

printf "${GREEN}Success! Deployment uploaded to Portal.${NC}\n"

read -r -p "Continue pushing to github? [y/n]: " key
if ! [[ "$key" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
abort
Expand All @@ -137,7 +170,7 @@ fi
printf "\n\n${YELLOW}Pushing changes...${NC}\n"
git commit -am "New release: $releaseVersion"
# push changes
git push origin $releaseBranchx
git push origin $releaseBranch

# create new tag
newTag=v$releaseVersion
Expand All @@ -155,6 +188,10 @@ git add .
git commit -m "Update documentation for $releaseVersion"
git push origin gh-pages

printf "\n${YELLOW}Checking out $releaseBranch to update snapshot version...${NC}\n"
git checkout $releaseBranch
git pull origin $releaseBranch

# update next snapshot version
printf "\n${YELLOW}Updating next snapshot version...${NC}\n"
sed -i.bak 's,^\(VERSION_NAME=\).*,\1'$nextSnapshotVersion',' gradle.properties
Expand All @@ -164,7 +201,7 @@ printf '\n\n\n'
read -r -p "Does this look right to you and the github action 'Release' has finished? [y/n]: " key
if [[ "$key" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
git pull
git commit -am "Update master with next snasphot version $nextSnapshotVersion"
git commit -am "Update master with next snapshot version $nextSnapshotVersion"
git push origin master
else
printf "${ORANGE}Make sure to update gradle.properties manually.${NC}\n"
Expand All @@ -178,6 +215,6 @@ cleanUp
printf "\n${GREEN}All done! ¯\_(ツ)_/¯ \n"
printf "Make sure you make a new release at https://github.com/mixpanel/mixpanel-android/releases/new\n"
printf "Also, do not forget to update our CHANGELOG (https://github.com/mixpanel/mixpanel-android/wiki/Changelog)\n"
printf "And finally, release the library from https://oss.sonatype.org/index.html\n\n${NC}"
printf "And finally, release the library from https://central.sonatype.com/publishing/deployments\n\n${NC}"

quit
Loading