diff --git a/.github/modules.json b/.github/modules.json new file mode 100644 index 0000000..d9724c5 --- /dev/null +++ b/.github/modules.json @@ -0,0 +1,30 @@ +{ + "analytics": { + "tag_prefix": "v", + "poms": [ + "pom.xml", + "mixpanel-java-extension-jackson/pom.xml" + ], + "prerequisite_poms": [], + "changelog": "CHANGELOG.md", + "readme": "README.md", + "artifact_ids": [ + "mixpanel-java", + "mixpanel-java-extension-jackson" + ] + }, + "openfeature": { + "tag_prefix": "openfeature/v", + "poms": [ + "openfeature-provider/pom.xml" + ], + "prerequisite_poms": [ + "pom.xml" + ], + "changelog": "openfeature-provider/CHANGELOG.md", + "readme": "openfeature-provider/README.md", + "artifact_ids": [ + "mixpanel-java-openfeature" + ] + } +} diff --git a/.github/scripts/generate-changelog.sh b/.github/scripts/generate-changelog.sh new file mode 100755 index 0000000..8f289c7 --- /dev/null +++ b/.github/scripts/generate-changelog.sh @@ -0,0 +1,80 @@ +#!/bin/bash +set -euo pipefail + +MODULE="$1" +VERSION_LABEL="$2" +REPO_URL="$3" +END_REF="${4:-HEAD}" + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +MODULES_JSON="$SCRIPT_DIR/../modules.json" + +TAG_PREFIX=$(jq -e -r --arg m "$MODULE" '.[$m].tag_prefix' "$MODULES_JSON") || { + echo "Unknown module: $MODULE. Valid modules: $(jq -r 'keys | join(", ")' "$MODULES_JSON")" >&2 + exit 1 +} +TAG_GLOB="${TAG_PREFIX}*" + +PREVIOUS_TAG=$(git tag --sort=-creatordate --list "$TAG_GLOB" | head -1 || true) + +if [ -z "$PREVIOUS_TAG" ]; then + RANGE="$END_REF" +else + RANGE="${PREVIOUS_TAG}..${END_REF}" +fi + +DATE=$(date +%Y-%m-%d) +SAFE_URL=$(printf '%s' "$REPO_URL" | sed 's|[&/\]|\\&|g') + +declare -a FEATURES=() +declare -a FIXES=() +declare -a CHORES=() + +while IFS= read -r line; do + [ -z "$line" ] && continue + MSG=$(echo "$line" | cut -d' ' -f2-) + + if [[ "$MSG" =~ ^(feat|fix|chore)\((${MODULE}|all)\):\ (.+) ]]; then + TYPE="${BASH_REMATCH[1]}" + DESC="${BASH_REMATCH[3]}" + + DESC=$(echo "$DESC" | sed -E "s|\(#([0-9]+)\)|([#\1](${SAFE_URL}/pull/\1))|g") + + case "$TYPE" in + feat) FEATURES+=("$DESC") ;; + fix) FIXES+=("$DESC") ;; + chore) CHORES+=("$DESC") ;; + esac + fi +done < <(git log --oneline "$RANGE") + +echo "## [${VERSION_LABEL}](${REPO_URL}/tree/${VERSION_LABEL}) (${DATE})" +echo "" + +if [ ${#FEATURES[@]} -gt 0 ]; then + echo "### Features" + for entry in "${FEATURES[@]}"; do + echo "- ${entry}" + done + echo "" +fi + +if [ ${#FIXES[@]} -gt 0 ]; then + echo "### Fixes" + for entry in "${FIXES[@]}"; do + echo "- ${entry}" + done + echo "" +fi + +if [ ${#CHORES[@]} -gt 0 ]; then + echo "### Chores" + for entry in "${CHORES[@]}"; do + echo "- ${entry}" + done + echo "" +fi + +if [ -n "$PREVIOUS_TAG" ]; then + echo "[Full Changelog](${REPO_URL}/compare/${PREVIOUS_TAG}...${VERSION_LABEL})" +fi diff --git a/.github/workflows/pr-title-check.yml b/.github/workflows/pr-title-check.yml new file mode 100644 index 0000000..a7901e1 --- /dev/null +++ b/.github/workflows/pr-title-check.yml @@ -0,0 +1,46 @@ +name: PR Title Check + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +permissions: + contents: read + +jobs: + check-title: + name: Validate PR title + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + sparse-checkout: .github/modules.json + sparse-checkout-cone-mode: false + + - name: Check PR title format + env: + PR_TITLE: ${{ github.event.pull_request.title }} + run: | + MODULE_LIST=$(jq -r 'keys | join("|")' .github/modules.json) + SCOPED_PATTERN="^(feat|fix|chore)\((${MODULE_LIST}|all)\): .+" + UNSCOPED_PATTERN="^(release|chore): .+" + + if [[ "$PR_TITLE" =~ $SCOPED_PATTERN ]] || [[ "$PR_TITLE" =~ $UNSCOPED_PATTERN ]]; then + echo "PR title is valid: $PR_TITLE" + exit 0 + fi + + echo "PR title does not match the required format." + echo "" + echo " Got: $PR_TITLE" + echo "" + echo "Expected one of:" + echo " feat(): description" + echo " fix(): description" + echo " chore(): description" + echo " chore: description" + echo " release: description" + echo "" + echo "Valid modules: ${MODULE_LIST//|/, }" + exit 1 diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 0000000..ae49189 --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,170 @@ +name: Prepare Release + +on: + workflow_dispatch: + inputs: + module: + description: 'Module to release (must match a key in .github/modules.json)' + required: true + type: string + default: 'analytics' + version: + description: 'Release version (e.g., 1.6.2)' + required: true + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + prepare: + name: "Prepare ${{ inputs.module }} ${{ inputs.version }}" + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up JDK 8 + uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 + with: + java-version: '8' + distribution: 'temurin' + + - name: Cache Maven dependencies + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Validate inputs + env: + MODULE: ${{ inputs.module }} + VERSION: ${{ inputs.version }} + run: | + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then + echo "::error::Invalid version format: $VERSION" + exit 1 + fi + jq -e --arg m "$MODULE" '.[$m]' .github/modules.json > /dev/null || { + echo "::error::Unknown module '$MODULE'. Valid modules: $(jq -r 'keys | join(", ")' .github/modules.json)" + exit 1 + } + + - name: Resolve module config + id: config + env: + MODULE: ${{ inputs.module }} + VERSION: ${{ inputs.version }} + run: | + MODULE_CONFIG=$(jq -e --arg m "$MODULE" '.[$m]' .github/modules.json) + TAG_PREFIX=$(echo "$MODULE_CONFIG" | jq -r '.tag_prefix') + echo "tag=${TAG_PREFIX}${VERSION}" >> "$GITHUB_OUTPUT" + echo "poms=$(echo "$MODULE_CONFIG" | jq -c '.poms')" >> "$GITHUB_OUTPUT" + echo "changelog=$(echo "$MODULE_CONFIG" | jq -r '.changelog')" >> "$GITHUB_OUTPUT" + echo "readme=$(echo "$MODULE_CONFIG" | jq -r '.readme')" >> "$GITHUB_OUTPUT" + echo "branch=release/${MODULE}/${VERSION}" >> "$GITHUB_OUTPUT" + + - name: Validate version not already released + env: + TAG: ${{ steps.config.outputs.tag }} + run: | + if git tag -l "$TAG" | grep -q .; then + echo "::error::Tag $TAG already exists" + exit 1 + fi + + - name: Create release branch + env: + BRANCH: ${{ steps.config.outputs.branch }} + run: git checkout -b "$BRANCH" + + - name: Bump versions in poms + env: + VERSION: ${{ inputs.version }} + POMS: ${{ steps.config.outputs.poms }} + run: | + for pom in $(echo "$POMS" | jq -r '.[]'); do + DIR=$(dirname "$pom") + (cd "$DIR" && mvn -q -B versions:set -DnewVersion="$VERSION" -DgenerateBackupPoms=false) + done + + - name: Update README + env: + REPO_URL: ${{ github.server_url }}/${{ github.repository }} + TAG: ${{ steps.config.outputs.tag }} + README: ${{ steps.config.outputs.readme }} + VERSION: ${{ inputs.version }} + run: | + DATE=$(date +"%B %d, %Y") + sed -i -E \ + "s|^##### _.*_ - \[.*\]\(.*\)|##### _${DATE}_ - [${TAG}](${REPO_URL}/releases/tag/${TAG})|" \ + "$README" + # Update inline ... examples in the README + sed -i -E "s|[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?|${VERSION}|g" "$README" + + - name: Generate changelog + env: + REPO_URL: ${{ github.server_url }}/${{ github.repository }} + MODULE: ${{ inputs.module }} + TAG: ${{ steps.config.outputs.tag }} + CHANGELOG_FILE: ${{ steps.config.outputs.changelog }} + run: | + CHANGELOG=$(.github/scripts/generate-changelog.sh \ + "$MODULE" "$TAG" "$REPO_URL" HEAD) + + if [ -f "$CHANGELOG_FILE" ]; then + { + printf '# Changelog\n\n%s\n' "$CHANGELOG" + sed '1{/^# Changelog$/d;}' "$CHANGELOG_FILE" + } > CHANGELOG.new.md + mv CHANGELOG.new.md "$CHANGELOG_FILE" + else + printf '# Changelog\n\n%s\n' "$CHANGELOG" > "$CHANGELOG_FILE" + fi + + - name: Commit and push + env: + MODULE: ${{ inputs.module }} + VERSION: ${{ inputs.version }} + BRANCH: ${{ steps.config.outputs.branch }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add -A + git commit -m "release: prepare ${MODULE} ${VERSION}" + git push origin "$BRANCH" + + - name: Create pull request + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MODULE: ${{ inputs.module }} + VERSION: ${{ inputs.version }} + TAG: ${{ steps.config.outputs.tag }} + CHANGELOG_FILE: ${{ steps.config.outputs.changelog }} + README: ${{ steps.config.outputs.readme }} + BRANCH: ${{ steps.config.outputs.branch }} + run: | + gh pr create \ + --title "release: prepare ${MODULE} ${VERSION}" \ + --body "$(cat <\` to \`${VERSION}\` in all module poms + - Updates \`${CHANGELOG_FILE}\` with changelog since last release + - Updates \`${README}\` with new version + + ### After merging + 1. Run the **Publish Release to Maven Central** workflow for \`${MODULE}\` (this will also create a draft GitHub release with tag \`${TAG}\`) + EOF + )" \ + --base master \ + --head "$BRANCH" diff --git a/.github/workflows/release-maven-central.yml b/.github/workflows/release-maven-central.yml new file mode 100644 index 0000000..bdc0088 --- /dev/null +++ b/.github/workflows/release-maven-central.yml @@ -0,0 +1,199 @@ +name: Publish Release to Maven Central + +on: + workflow_dispatch: + inputs: + module: + description: 'Module to publish (must match a key in .github/modules.json)' + required: true + type: string + default: 'analytics' + confirm_version: + description: 'Confirm the version to publish (must match the in every module pom)' + required: true + type: string + ref: + description: 'Git ref to publish (branch, tag, or SHA). Leave empty for the default branch.' + required: false + type: string + default: '' + +concurrency: + group: ${{ inputs.module }}-maven-publish + cancel-in-progress: false + +permissions: + contents: write + +jobs: + publish: + name: "Publish ${{ inputs.module }} to Maven Central" + runs-on: ubuntu-latest + environment: release + + steps: + - name: Validate secrets + run: | + missing="" + if [ -z "${{ secrets.MAVEN_CENTRAL_USERNAME }}" ]; then missing="$missing MAVEN_CENTRAL_USERNAME"; fi + if [ -z "${{ secrets.MAVEN_CENTRAL_TOKEN }}" ]; then missing="$missing MAVEN_CENTRAL_TOKEN"; fi + if [ -z "${{ secrets.GPG_PRIVATE_KEY }}" ]; then missing="$missing GPG_PRIVATE_KEY"; fi + if [ -z "${{ secrets.GPG_PASSPHRASE }}" ]; then missing="$missing GPG_PASSPHRASE"; fi + if [ -n "$missing" ]; then + echo "::error::Missing required secrets:$missing" + exit 1 + fi + echo "All required secrets are configured" + + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: ${{ inputs.ref }} + + - name: Resolve module config + id: module + env: + MODULE: ${{ inputs.module }} + CONFIRM_VERSION: ${{ inputs.confirm_version }} + run: | + if ! [[ "$CONFIRM_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then + echo "::error::Invalid version format: $CONFIRM_VERSION" + exit 1 + fi + MODULE_CONFIG=$(jq -e --arg m "$MODULE" '.[$m]' .github/modules.json) || { + echo "::error::Unknown module '$MODULE'. Valid modules: $(jq -r 'keys | join(", ")' .github/modules.json)" + exit 1 + } + TAG_PREFIX=$(echo "$MODULE_CONFIG" | jq -r '.tag_prefix') + echo "tag=${TAG_PREFIX}${CONFIRM_VERSION}" >> "$GITHUB_OUTPUT" + echo "poms=$(echo "$MODULE_CONFIG" | jq -c '.poms')" >> "$GITHUB_OUTPUT" + echo "prerequisite_poms=$(echo "$MODULE_CONFIG" | jq -c '.prerequisite_poms // []')" >> "$GITHUB_OUTPUT" + echo "changelog=$(echo "$MODULE_CONFIG" | jq -r '.changelog')" >> "$GITHUB_OUTPUT" + + - name: Set up JDK 8 + uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 + with: + java-version: '8' + distribution: 'temurin' + + - name: Cache Maven dependencies + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Validate pom versions + env: + CONFIRM_VERSION: ${{ inputs.confirm_version }} + POMS: ${{ steps.module.outputs.poms }} + run: | + for pom in $(echo "$POMS" | jq -r '.[]'); do + DIR=$(dirname "$pom") + POM_VERSION=$(cd "$DIR" && mvn -q -B help:evaluate -Dexpression=project.version -DforceStdout) + if [ "$CONFIRM_VERSION" != "$POM_VERSION" ]; then + echo "::error::Input version '$CONFIRM_VERSION' does not match $pom version '$POM_VERSION'" + exit 1 + fi + done + echo "Version confirmed: $CONFIRM_VERSION" + + - name: Import GPG key + env: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + run: | + echo "$GPG_PRIVATE_KEY" | base64 --decode | gpg --batch --import + echo "allow-preset-passphrase" >> ~/.gnupg/gpg-agent.conf + echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf + gpg --list-secret-keys --keyid-format LONG + + - name: Configure Maven settings + env: + MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} + run: | + mkdir -p ~/.m2 + cat > ~/.m2/settings.xml << EOF + + + + central + ${MAVEN_CENTRAL_USERNAME} + ${MAVEN_CENTRAL_TOKEN} + + + + EOF + + - name: Install prerequisite modules locally + env: + PREREQS: ${{ steps.module.outputs.prerequisite_poms }} + run: | + for pom in $(echo "$PREREQS" | jq -r '.[]'); do + DIR=$(dirname "$pom") + (cd "$DIR" && mvn -B clean install -DskipTests -Dgpg.skip=true) + done + + - name: Test all modules + env: + POMS: ${{ steps.module.outputs.poms }} + run: | + for pom in $(echo "$POMS" | jq -r '.[]'); do + DIR=$(dirname "$pom") + (cd "$DIR" && mvn -B clean test) + done + + - name: Install module poms locally + env: + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + POMS: ${{ steps.module.outputs.poms }} + run: | + # Install in order so sibling poms in this module can resolve each other from the local repo + for pom in $(echo "$POMS" | jq -r '.[]'); do + DIR=$(dirname "$pom") + (cd "$DIR" && mvn -B clean install -DskipTests -Dgpg.passphrase=$GPG_PASSPHRASE) + done + + - name: Deploy to Maven Central staging + env: + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + POMS: ${{ steps.module.outputs.poms }} + run: | + for pom in $(echo "$POMS" | jq -r '.[]'); do + DIR=$(dirname "$pom") + (cd "$DIR" && mvn -B deploy -DskipTests -Dgpg.passphrase=$GPG_PASSPHRASE) + done + + - name: Create draft GitHub release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.module.outputs.tag }} + CHANGELOG: ${{ steps.module.outputs.changelog }} + TARGET_REF: ${{ inputs.ref }} + run: | + NOTES=$(sed -n "/^## \[${TAG}\]/,/^## \[/{/^## \[${TAG}\]/d;/^## \[/d;p;}" "$CHANGELOG") + + ARGS=(--draft --title "$TAG" --notes "$NOTES") + if [ -n "$TARGET_REF" ]; then + ARGS+=(--target "$TARGET_REF") + fi + + gh release create "$TAG" "${ARGS[@]}" + + - name: Summary + env: + MODULE: ${{ inputs.module }} + CONFIRM_VERSION: ${{ inputs.confirm_version }} + run: | + { + echo "## ${MODULE} Publishing Summary" + echo "" + echo "Version **${CONFIRM_VERSION}** of \`mixpanel-java\` and \`mixpanel-java-extension-jackson\` has been staged to Maven Central." + echo "" + echo "### Next Steps" + echo "1. Go to [Maven Central Portal](https://central.sonatype.com/publishing/deployments)" + echo "2. Review the staged artifacts (one bundle per module)" + echo "3. Click **Publish** on each to make the release live" + echo "4. Review and publish the [draft GitHub release](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases) — publishing creates the git tag" + } >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release-openfeature.yml b/.github/workflows/release-openfeature.yml deleted file mode 100644 index 3908662..0000000 --- a/.github/workflows/release-openfeature.yml +++ /dev/null @@ -1,107 +0,0 @@ -name: Release OpenFeature Provider to Maven Central - -on: - workflow_dispatch: - inputs: - version: - description: 'Version to release (e.g., 0.1.0)' - required: true - type: string - -jobs: - release: - runs-on: ubuntu-latest - outputs: - version: ${{ steps.set-version.outputs.version }} - - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Set up JDK 8 - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 - with: - java-version: '11' - distribution: 'temurin' - - - name: Cache Maven dependencies - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - - name: Import GPG key - env: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - run: | - echo "$GPG_PRIVATE_KEY" | base64 --decode | gpg --batch --import - echo "allow-preset-passphrase" >> ~/.gnupg/gpg-agent.conf - echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf - gpg --list-secret-keys --keyid-format LONG - - - name: Configure Maven settings - env: - MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} - run: | - mkdir -p ~/.m2 - cat > ~/.m2/settings.xml << EOF - - - - central - ${MAVEN_CENTRAL_USERNAME} - ${MAVEN_CENTRAL_TOKEN} - - - - EOF - - - name: Set version - id: set-version - run: | - VERSION=${{ github.event.inputs.version }} - echo "VERSION=$VERSION" >> $GITHUB_ENV - echo "version=$VERSION" >> $GITHUB_OUTPUT - cd openfeature-provider - mvn versions:set -DnewVersion=$VERSION -DgenerateBackupPoms=false - - - name: Build and install Main SDK locally - run: mvn clean install -DskipTests -Dgpg.skip=true - - - name: Run tests - OpenFeature Provider - run: | - cd openfeature-provider - mvn clean test - - - name: Deploy OpenFeature Provider to Maven Central - env: - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - run: | - cd openfeature-provider - mvn clean deploy -Dgpg.passphrase=$GPG_PASSPHRASE - - verify: - needs: release - runs-on: ubuntu-latest - if: success() - - steps: - - name: Wait for Maven Central sync - run: sleep 300 # Wait 5 minutes for synchronization - - - name: Verify artifact on Maven Central - run: | - VERSION=${{ needs.release.outputs.version }} - - RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" https://repo1.maven.org/maven2/com/mixpanel/mixpanel-java-openfeature/${VERSION}/mixpanel-java-openfeature-${VERSION}.jar) - if [ $RESPONSE -eq 200 ]; then - echo "✅ OpenFeature Provider successfully published to Maven Central" - else - echo "⚠️ OpenFeature Provider not yet available on Maven Central (HTTP $RESPONSE)" - fi - - echo "Note: Artifacts may take up to 30 minutes to appear on Maven Central" diff --git a/.github/workflows/release-snapshot.yml b/.github/workflows/release-snapshot.yml new file mode 100644 index 0000000..b1addc5 --- /dev/null +++ b/.github/workflows/release-snapshot.yml @@ -0,0 +1,170 @@ +name: Publish Snapshots to Maven Central + +on: + push: + branches: + - master + +permissions: + contents: read + +jobs: + list-modules: + runs-on: ubuntu-latest + outputs: + modules: ${{ steps.modules.outputs.list }} + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + sparse-checkout: .github/modules.json + sparse-checkout-cone-mode: false + + - name: List modules + id: modules + run: echo "list=$(jq -c 'keys' .github/modules.json)" >> "$GITHUB_OUTPUT" + + publish-snapshot: + needs: list-modules + strategy: + fail-fast: false + matrix: + module: ${{ fromJson(needs.list-modules.outputs.modules) }} + concurrency: + group: ${{ matrix.module }}-snapshot-publish + cancel-in-progress: true + name: "Publish ${{ matrix.module }} snapshot" + runs-on: ubuntu-latest + environment: release + + steps: + - name: Validate secrets + run: | + missing="" + if [ -z "${{ secrets.MAVEN_CENTRAL_USERNAME }}" ]; then missing="$missing MAVEN_CENTRAL_USERNAME"; fi + if [ -z "${{ secrets.MAVEN_CENTRAL_TOKEN }}" ]; then missing="$missing MAVEN_CENTRAL_TOKEN"; fi + if [ -n "$missing" ]; then + echo "::error::Missing required secrets:$missing" + exit 1 + fi + + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Resolve module config + id: config + env: + MODULE: ${{ matrix.module }} + run: | + MODULE_CONFIG=$(jq -e --arg m "$MODULE" '.[$m]' .github/modules.json) + echo "poms=$(echo "$MODULE_CONFIG" | jq -c '.poms')" >> "$GITHUB_OUTPUT" + echo "prerequisite_poms=$(echo "$MODULE_CONFIG" | jq -c '.prerequisite_poms // []')" >> "$GITHUB_OUTPUT" + echo "primary_artifact_id=$(echo "$MODULE_CONFIG" | jq -r '.artifact_ids[0]')" >> "$GITHUB_OUTPUT" + + - name: Set up JDK 8 + uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 + with: + java-version: '8' + distribution: 'temurin' + + - name: Cache Maven dependencies + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Configure Maven settings + env: + MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} + run: | + mkdir -p ~/.m2 + cat > ~/.m2/settings.xml << EOF + + + + central + ${MAVEN_CENTRAL_USERNAME} + ${MAVEN_CENTRAL_TOKEN} + + + + EOF + + - name: Compute snapshot version + id: snapshot + env: + POMS: ${{ steps.config.outputs.poms }} + run: | + PRIMARY=$(echo "$POMS" | jq -r '.[0]') + PRIMARY_DIR=$(dirname "$PRIMARY") + CURRENT=$(cd "$PRIMARY_DIR" && mvn -q -B help:evaluate -Dexpression=project.version -DforceStdout) + + if [[ "$CURRENT" == *-SNAPSHOT ]]; then + SNAPSHOT_VERSION="$CURRENT" + else + BASE="${CURRENT%%-*}" + IFS='.' read -r MAJ MIN PATCH <<< "$BASE" + SNAPSHOT_VERSION="${MAJ}.${MIN}.$((PATCH + 1))-SNAPSHOT" + fi + + echo "version=$SNAPSHOT_VERSION" >> "$GITHUB_OUTPUT" + + - name: Set snapshot version on module poms + env: + SNAPSHOT_VERSION: ${{ steps.snapshot.outputs.version }} + POMS: ${{ steps.config.outputs.poms }} + run: | + for pom in $(echo "$POMS" | jq -r '.[]'); do + DIR=$(dirname "$pom") + (cd "$DIR" && mvn -q -B versions:set -DnewVersion="$SNAPSHOT_VERSION" -DgenerateBackupPoms=false) + done + + - name: Install prerequisite modules locally + env: + PREREQS: ${{ steps.config.outputs.prerequisite_poms }} + run: | + for pom in $(echo "$PREREQS" | jq -r '.[]'); do + DIR=$(dirname "$pom") + (cd "$DIR" && mvn -B clean install -DskipTests -Dgpg.skip=true) + done + + - name: Deploy snapshots to Maven Central + env: + POMS: ${{ steps.config.outputs.poms }} + run: | + for pom in $(echo "$POMS" | jq -r '.[]'); do + DIR=$(dirname "$pom") + (cd "$DIR" && mvn -B clean deploy -DskipTests -Dgpg.skip=true) + done + + - name: Summary + env: + MODULE: ${{ matrix.module }} + SNAPSHOT_VERSION: ${{ steps.snapshot.outputs.version }} + ARTIFACT_ID: ${{ steps.config.outputs.primary_artifact_id }} + run: | + { + echo "## ${MODULE} Snapshot Published" + echo "" + echo "Version **${SNAPSHOT_VERSION}** of \`${ARTIFACT_ID}\` (and any sibling artifacts in this module) has been published to Maven Central Snapshots." + echo "" + echo "### Usage" + echo '```xml' + echo "" + echo " " + echo " central-snapshots" + echo " https://central.sonatype.com/repository/maven-snapshots/" + echo " true" + echo " " + echo "" + echo "" + echo "" + echo " com.mixpanel" + echo " ${ARTIFACT_ID}" + echo " ${SNAPSHOT_VERSION}" + echo "" + echo '```' + } >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 185ad0c..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,184 +0,0 @@ -name: Release to Maven Central - -on: - push: - tags: - - 'v*' - workflow_dispatch: - inputs: - version: - description: 'Version to release (e.g., 1.5.4)' - required: true - type: string - -jobs: - release: - runs-on: ubuntu-latest - outputs: - version: ${{ steps.set-version.outputs.version }} - - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Set up JDK 8 - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 - with: - java-version: '8' - distribution: 'temurin' - - - name: Cache Maven dependencies - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - - name: Import GPG key - env: - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - run: | - echo "$GPG_PRIVATE_KEY" | base64 --decode | gpg --batch --import - echo "allow-preset-passphrase" >> ~/.gnupg/gpg-agent.conf - echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf - gpg --list-secret-keys --keyid-format LONG - - - name: Configure Maven settings - env: - MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} - run: | - mkdir -p ~/.m2 - cat > ~/.m2/settings.xml << EOF - - - - central - ${MAVEN_CENTRAL_USERNAME} - ${MAVEN_CENTRAL_TOKEN} - - - - EOF - - - name: Set version from tag - id: set-version - if: startsWith(github.ref, 'refs/tags/') - run: | - VERSION=${GITHUB_REF#refs/tags/v} - echo "VERSION=$VERSION" >> $GITHUB_ENV - echo "version=$VERSION" >> $GITHUB_OUTPUT - mvn versions:set -DnewVersion=$VERSION -DgenerateBackupPoms=false - cd mixpanel-java-extension-jackson - mvn versions:set -DnewVersion=$VERSION -DgenerateBackupPoms=false - cd .. - - - name: Set version from input - id: set-version-input - if: github.event_name == 'workflow_dispatch' - run: | - VERSION=${{ github.event.inputs.version }} - echo "VERSION=$VERSION" >> $GITHUB_ENV - echo "version=$VERSION" >> $GITHUB_OUTPUT - mvn versions:set -DnewVersion=$VERSION -DgenerateBackupPoms=false - cd mixpanel-java-extension-jackson - mvn versions:set -DnewVersion=$VERSION -DgenerateBackupPoms=false - cd .. - - - name: Build and install Main SDK locally - run: mvn clean install -DskipTests -Dgpg.skip=true - - - name: Run tests - Main SDK - run: mvn test - - - name: Run tests - Jackson Extension - run: | - cd mixpanel-java-extension-jackson - mvn clean test - cd .. - - - name: Deploy to Maven Central - env: - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - run: | - mvn clean deploy -Dgpg.passphrase=$GPG_PASSPHRASE - cd mixpanel-java-extension-jackson - mvn clean deploy -Dgpg.passphrase=$GPG_PASSPHRASE - cd .. - - - name: Create GitHub Release - if: startsWith(github.ref, 'refs/tags/') - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const releaseBody = `## Mixpanel Java SDK v${process.env.VERSION} - - ### Maven - Main SDK - \`\`\`xml - - com.mixpanel - mixpanel-java - ${process.env.VERSION} - - \`\`\` - - ### Maven - Jackson Extension (Optional) - \`\`\`xml - - com.mixpanel - mixpanel-java-extension-jackson - ${process.env.VERSION} - - \`\`\` - - ### Changes - See [CHANGELOG](https://github.com/mixpanel/mixpanel-java/blob/master/CHANGELOG.md) for details. - - ### Links - - [Maven Central - Main SDK](https://central.sonatype.com/artifact/com.mixpanel/mixpanel-java/${process.env.VERSION}) - - [Maven Central - Jackson Extension](https://central.sonatype.com/artifact/com.mixpanel/mixpanel-java-extension-jackson/${process.env.VERSION}) - - [JavaDoc](http://mixpanel.github.io/mixpanel-java/)`; - - await github.rest.repos.createRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - tag_name: context.ref.replace('refs/tags/', ''), - name: `Release ${process.env.VERSION}`, - body: releaseBody, - draft: false, - prerelease: false - }); - - verify: - needs: release - runs-on: ubuntu-latest - if: success() - - steps: - - name: Wait for Maven Central sync - run: sleep 300 # Wait 5 minutes for synchronization - - - name: Verify artifacts on Maven Central - run: | - VERSION=${{ needs.release.outputs.version }} - - # Verify main SDK - RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" https://repo1.maven.org/maven2/com/mixpanel/mixpanel-java/${VERSION}/mixpanel-java-${VERSION}.jar) - if [ $RESPONSE -eq 200 ]; then - echo "✅ Main SDK successfully published to Maven Central" - else - echo "⚠️ Main SDK not yet available on Maven Central (HTTP $RESPONSE)" - fi - - # Verify Jackson extension - RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" https://repo1.maven.org/maven2/com/mixpanel/mixpanel-java-extension-jackson/${VERSION}/mixpanel-java-extension-jackson-${VERSION}.jar) - if [ $RESPONSE -eq 200 ]; then - echo "✅ Jackson extension successfully published to Maven Central" - else - echo "⚠️ Jackson extension not yet available on Maven Central (HTTP $RESPONSE)" - fi - - echo "Note: Artifacts may take up to 30 minutes to appear on Maven Central" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2ff4a8e..9aefdd2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ .classpath .metadata target/ -.vscode/ \ No newline at end of file +.vscode/ +.claude/ +.idea/ +*.iml \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..95d7e6a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## [v1.8.0](https://github.com/mixpanel/mixpanel-java/tree/v1.8.0) (2026-04-09) + +See the [v1.8.0 release notes](https://github.com/mixpanel/mixpanel-java/releases/tag/v1.8.0) +and prior tags on the [releases page](https://github.com/mixpanel/mixpanel-java/releases) +for the full history. diff --git a/CLAUDE.md b/CLAUDE.md index fb0b356..6eed961 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,40 +6,6 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co This is the official Mixpanel tracking library for Java - a production-ready library for sending analytics events and user profile updates to Mixpanel from Java server-side applications. -## Release Process - -### Quick Commands for Releases - -```bash -# 1. Update version (remove -SNAPSHOT from pom.xml) -mvn versions:set -DnewVersion=1.5.4 - -# 2. Run tests -mvn clean test - -# 3. Deploy to Maven Central Portal -mvn clean deploy - -# 4. After release, prepare next version -mvn versions:set -DnewVersion=1.5.5-SNAPSHOT -``` - -### Key Files -- **RELEASE.md**: Complete release documentation with step-by-step instructions -- **.github/workflows/release.yml**: Automated release workflow triggered by version tags -- **.github/workflows/ci.yml**: Continuous integration for all PRs and master commits - -### Maven Central Portal -- The project uses the new Maven Central Portal (not the deprecated OSSRH) -- Deployments are visible at: https://central.sonatype.com/publishing/deployments -- Published artifacts: https://central.sonatype.com/artifact/com.mixpanel/mixpanel-java - -### Required GitHub Secrets for CI/CD -- `GPG_PRIVATE_KEY`: Base64-encoded GPG private key -- `GPG_PASSPHRASE`: GPG key passphrase -- `MAVEN_CENTRAL_USERNAME`: Maven Central Portal username -- `MAVEN_CENTRAL_TOKEN`: Maven Central Portal token - ## Build and Development Commands ```bash diff --git a/README.md b/README.md index b8aed0d..15597fe 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ This is the official Mixpanel tracking library for Java. +##### _April 09, 2026_ - [v1.8.0](https://github.com/mixpanel/mixpanel-java/releases/tag/v1.8.0) + ## Latest Version See the [releases page](https://github.com/mixpanel/mixpanel-java/releases) for the latest version. diff --git a/openfeature-provider/CHANGELOG.md b/openfeature-provider/CHANGELOG.md new file mode 100644 index 0000000..2e27bba --- /dev/null +++ b/openfeature-provider/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +## [openfeature/v0.1.1](https://github.com/mixpanel/mixpanel-java/tree/openfeature/v0.1.1) (2026-04-22) + +### Fixes +- handle JSONObject/JSONArray in objectToValue ([#65](https://github.com/mixpanel/mixpanel-java/pull/65)) + +### Chores +- bump version to 0.1.1 ([#66](https://github.com/mixpanel/mixpanel-java/pull/66)) + +[Full Changelog](https://github.com/mixpanel/mixpanel-java/compare/openfeature/v0.1.0...openfeature/v0.1.1) + +## [openfeature/v0.1.0](https://github.com/mixpanel/mixpanel-java/tree/openfeature/v0.1.0) (2026-04-09) + +Initial release of the Mixpanel Java OpenFeature provider. See the +[v0.1.0 release notes](https://github.com/mixpanel/mixpanel-java/releases/tag/openfeature/v0.1.0) +for details. diff --git a/openfeature-provider/README.md b/openfeature-provider/README.md index a781b13..e0441eb 100644 --- a/openfeature-provider/README.md +++ b/openfeature-provider/README.md @@ -4,6 +4,8 @@ [![OpenFeature](https://img.shields.io/badge/OpenFeature-compatible-green)](https://openfeature.dev/) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/mixpanel/mixpanel-java/blob/master/LICENSE) +##### _April 22, 2026_ - [openfeature/v0.1.1](https://github.com/mixpanel/mixpanel-java/releases/tag/openfeature/v0.1.1) + An [OpenFeature](https://openfeature.dev/) provider that integrates Mixpanel's feature flags with the OpenFeature Java SDK. This allows you to use Mixpanel's feature flagging capabilities through OpenFeature's standardized, vendor-agnostic API. ## Overview diff --git a/openfeature-provider/RELEASE.md b/openfeature-provider/RELEASE.md deleted file mode 100644 index 02de7a0..0000000 --- a/openfeature-provider/RELEASE.md +++ /dev/null @@ -1,53 +0,0 @@ -# Releasing the OpenFeature Provider - -The OpenFeature provider (`com.mixpanel:mixpanel-java-openfeature`) is published to Maven Central independently from the core SDK. - -## Prerequisites - -The following GitHub secrets must be configured (shared with the core SDK release workflow): - -- `GPG_PRIVATE_KEY` — Base64-encoded GPG private key -- `GPG_PASSPHRASE` — GPG key passphrase -- `MAVEN_CENTRAL_USERNAME` — Maven Central Portal username -- `MAVEN_CENTRAL_TOKEN` — Maven Central Portal token - -## Releasing via GitHub Actions - -1. Go to **Actions** > **Release OpenFeature Provider to Maven Central** -2. Click **Run workflow** -3. Enter the version to release (e.g., `0.1.0`) -4. The workflow will: - - Build and install the core SDK locally - - Run OpenFeature provider tests - - Sign artifacts with GPG - - Deploy to Maven Central Portal - - Wait 5 minutes then verify the artifact is available - -After deployment, artifacts are visible at: -- Deployments: https://central.sonatype.com/publishing/deployments -- Published: https://central.sonatype.com/artifact/com.mixpanel/mixpanel-java-openfeature - -Note: `autoPublish` is set to `false` in `pom.xml`, so you may need to manually publish the deployment from the Sonatype Central Portal. - -## Releasing manually - -```bash -# 1. Set the version -cd openfeature-provider -mvn versions:set -DnewVersion=0.1.0 -DgenerateBackupPoms=false - -# 2. Build and install the core SDK locally -cd .. -mvn clean install -DskipTests -Dgpg.skip=true - -# 3. Run tests -cd openfeature-provider -mvn clean test - -# 4. Deploy -mvn clean deploy -Dgpg.passphrase= -``` - -## Versioning - -The OpenFeature provider is versioned independently from the core SDK. The current core SDK dependency version is pinned in `pom.xml` — update it when the provider needs features from a newer core SDK release.