diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 02f1e41e..1acb219a 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -109,15 +109,13 @@ npm test ### Native SDK Dependencies - iOS: `Mixpanel-swift@5.1.0` -- Android: `mixpanel-android@8.2.0` +- Android: `mixpanel-android@8.7.0` ## Sample Apps Located in `Samples/` directory: -- `SimpleMixpanel` - Minimal integration example (used in CI) -- `MixpanelDemo` - Full API demonstration -- `ContextAPIMixpanel` - React Context integration -- `MixpanelExpo` - Expo project example -- `MixpanelExample`, `MixpanelStarter` - Additional examples +- `MixpanelExample` - Core integration patterns (used in CI) +- `MixpanelExpo` - Expo project example with full API reference +- `MixpanelStarter` - Production-ready architecture with TypeScript ## Generating Documentation ```bash diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fa39aece..993b565d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,3 +5,11 @@ updates: directory: "/" # Tells Dependabot to scan root directory only schedule: interval: "daily" + cooldown: + default-days: 30 + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + cooldown: + default-days: 30 diff --git a/.github/modules.json b/.github/modules.json new file mode 100644 index 00000000..2a9823e6 --- /dev/null +++ b/.github/modules.json @@ -0,0 +1,9 @@ +{ + "analytics": { + "tag_prefix": "v", + "package_json": "package.json", + "changelog": "CHANGELOG.md", + "readme": "README.md", + "package_name": "mixpanel-react-native" + } +} diff --git a/.github/scripts/generate-changelog.sh b/.github/scripts/generate-changelog.sh new file mode 100755 index 00000000..7ebd946b --- /dev/null +++ b/.github/scripts/generate-changelog.sh @@ -0,0 +1,87 @@ +#!/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-) + + # feat / fix: include whether bare, scoped to our module, or scoped to + # `all` (cross-cutting changes that appear in every module's changelog). + # chore: include only when explicitly scoped to our module or `all` — + # bare `chore:` is the convention for changes intentionally hidden from + # the changelog (release prep PRs, CI tweaks, lockfile bumps, internal + # docs). + if [[ "$MSG" =~ ^(feat|fix)(\((${MODULE}|all)\))?:\ (.+) ]]; then + TYPE="${BASH_REMATCH[1]}" + DESC="${BASH_REMATCH[4]}" + DESC=$(echo "$DESC" | sed -E "s|\(#([0-9]+)\)|([#\1](${SAFE_URL}/pull/\1))|g") + case "$TYPE" in + feat) FEATURES+=("$DESC") ;; + fix) FIXES+=("$DESC") ;; + esac + elif [[ "$MSG" =~ ^chore\((${MODULE}|all)\):\ (.+) ]]; then + DESC="${BASH_REMATCH[2]}" + DESC=$(echo "$DESC" | sed -E "s|\(#([0-9]+)\)|([#\1](${SAFE_URL}/pull/\1))|g") + CHORES+=("$DESC") + 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/node.js.yml b/.github/workflows/node.js.yml index d8dcea61..ec804562 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -17,9 +17,9 @@ jobs: node-version: [18.x] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: ${{ matrix.node-version }} - run: npm install @@ -34,21 +34,21 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: ${{ matrix.node-version }} - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 with: distribution: "temurin" java-version: "17" - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@48b5f213c81028ace310571dc5ec0fbbca0b2947 # v4 - name: Install dependencies run: | @@ -57,7 +57,7 @@ jobs: npm i react-native-gradle-plugin - name: Test Integration - Install dependencies - working-directory: ./Samples/SimpleMixpanel + working-directory: ./Samples/MixpanelExample run: yarn install - name: Enable KVM @@ -67,7 +67,7 @@ jobs: sudo udevadm trigger --name-match=kvm - name: AVD cache - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 id: avd-cache with: path: | @@ -77,7 +77,7 @@ jobs: - name: Create AVD and generate snapshot for caching if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 + uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2 env: GRADLE_OPTS: -Xmx4096m -Dorg.gradle.daemon=false JAVA_OPTS: -Xmx4096m @@ -89,11 +89,11 @@ jobs: force-avd-creation: false emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: false - working-directory: ./Samples/SimpleMixpanel + working-directory: ./Samples/MixpanelExample script: echo "Generated AVD snapshot for caching." - name: Run Android Tests - uses: reactivecircus/android-emulator-runner@v2 + uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2 env: GRADLE_OPTS: -Xmx4096m -Dorg.gradle.daemon=false JAVA_OPTS: -Xmx4096m @@ -106,7 +106,7 @@ jobs: emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true emulator-boot-timeout: 900 - working-directory: ./Samples/SimpleMixpanel + working-directory: ./Samples/MixpanelExample script: | echo "org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError" >> android/gradle.properties echo "org.gradle.daemon=false" >> android/gradle.properties @@ -119,19 +119,21 @@ jobs: node-version: [18.x] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm install -g react-native-cli - name: Test Integration - Install dependencies - working-directory: ./Samples/SimpleMixpanel + working-directory: ./Samples/MixpanelExample run: npm install - name: Setup iOS - working-directory: ./Samples/SimpleMixpanel/ios - run: pod install --repo-update + working-directory: ./Samples/MixpanelExample/ios + run: | + rm -f Podfile.lock + pod install --repo-update - name: Test iOS - working-directory: ./Samples/SimpleMixpanel + working-directory: ./Samples/MixpanelExample run: react-native run-ios diff --git a/.github/workflows/pr-title-check.yml b/.github/workflows/pr-title-check.yml new file mode 100644 index 00000000..59867ed9 --- /dev/null +++ b/.github/workflows/pr-title-check.yml @@ -0,0 +1,51 @@ +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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + 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) + # Scope is optional. Bare, scoped to a known module, or scoped to + # `all` (cross-cutting changes that appear in every module's + # changelog) all pass. + MAIN_PATTERN="^(feat|fix|chore)(\((${MODULE_LIST}|all)\))?: .+" + RELEASE_PATTERN="^release: .+" + + if [[ "$PR_TITLE" =~ $MAIN_PATTERN ]] || [[ "$PR_TITLE" =~ $RELEASE_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 " feat(|all): description" + echo " fix(|all): description" + echo " chore(|all): description" + echo " release: description" + echo "" + echo "Valid scopes: ${MODULE_LIST//|/, }, all" + exit 1 diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 00000000..a4e99dbb --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,206 @@ +name: Prepare Release + +on: + workflow_dispatch: + inputs: + module: + description: 'Module to release (must match a key in .github/modules.json)' + required: true + type: string + version: + description: 'Release version (e.g., 1.3.0 or 1.3.0-beta.1)' + required: true + type: string + +permissions: + contents: write + pull-requests: write + +concurrency: + group: prepare-release-${{ inputs.module }} + cancel-in-progress: false + +jobs: + prepare: + name: "Prepare ${{ inputs.module }} ${{ inputs.version }}" + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - 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}" + echo "package_json=$(echo "$MODULE_CONFIG" | jq -r '.package_json')" + echo "changelog=$(echo "$MODULE_CONFIG" | jq -r '.changelog')" + echo "readme=$(echo "$MODULE_CONFIG" | jq -r '.readme')" + echo "package_name=$(echo "$MODULE_CONFIG" | jq -r '.package_name')" + 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: Clean up existing release branch and PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: ${{ steps.config.outputs.branch }} + run: | + EXISTING_PR=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || true) + if [[ -n "$EXISTING_PR" ]]; then + echo "Closing existing PR #$EXISTING_PR and deleting branch" + gh pr close "$EXISTING_PR" --delete-branch + elif git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then + echo "Deleting orphaned branch $BRANCH" + git push origin --delete "$BRANCH" + fi + + - name: Create release branch + env: + BRANCH: ${{ steps.config.outputs.branch }} + run: git checkout -b "$BRANCH" + + - name: Bump version in package.json + env: + VERSION: ${{ inputs.version }} + PACKAGE_JSON: ${{ steps.config.outputs.package_json }} + run: | + OLD_VERSION=$(jq -r '.version' "$PACKAGE_JSON") + if [ -z "$OLD_VERSION" ] || [ "$OLD_VERSION" = "null" ]; then + echo "::error::Could not read current version from $PACKAGE_JSON" + exit 1 + fi + echo "Bumping ${OLD_VERSION} -> ${VERSION}" + + # Atomic write via tmp file. jq preserves 2-space indentation that + # matches Prettier's default (and the existing file). + jq --indent 2 --arg v "$VERSION" '.version = $v' "$PACKAGE_JSON" > "$PACKAGE_JSON.tmp" + mv "$PACKAGE_JSON.tmp" "$PACKAGE_JSON" + + - name: Update README version header + env: + REPO_URL: ${{ github.server_url }}/${{ github.repository }} + TAG: ${{ steps.config.outputs.tag }} + README: ${{ steps.config.outputs.readme }} + run: | + DATE=$(date +"%B %d, %Y") + # Replace the version header line. + # The `1,/pat/` address range bounds the substitution to lines from + # the start of the file through the first match, so a README that + # accidentally contains a second matching line is left untouched. + sed -i -E \ + "1,/^##### _.*_ - \[.*\]\(.*\)\$/ s|^##### _.*_ - \[.*\]\(.*\)\$|##### _${DATE}_ - [${TAG}](${REPO_URL}/releases/tag/${TAG})|" \ + "$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: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version-file: .nvmrc + + - name: Regenerate JSDoc + run: | + npm install + npx --yes jsdoc index.js -d ./docs -r README.md + + - name: Commit and push + env: + MODULE: ${{ inputs.module }} + VERSION: ${{ inputs.version }} + BRANCH: ${{ steps.config.outputs.branch }} + PACKAGE_JSON: ${{ steps.config.outputs.package_json }} + CHANGELOG_FILE: ${{ steps.config.outputs.changelog }} + README: ${{ steps.config.outputs.readme }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add "$PACKAGE_JSON" "$CHANGELOG_FILE" "$README" docs + 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 }} + PACKAGE_JSON: ${{ steps.config.outputs.package_json }} + 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 <> "$GITHUB_OUTPUT" + + echo "Resolved tag '$TAG' -> module '$MODULE', version '$VERSION'" + + - name: Verify tag commit is on master + env: + TAG: ${{ github.ref_name }} + run: | + git fetch origin master + TAG_SHA=$(git rev-parse HEAD) + if ! git merge-base --is-ancestor "$TAG_SHA" origin/master; then + echo "::error::Tag '$TAG' ($TAG_SHA) is not an ancestor of origin/master." + echo "Tags must be pushed from a commit on the master branch." + exit 1 + fi + + - name: Validate package.json version matches tag + env: + VERSION: ${{ steps.module.outputs.version }} + PACKAGE_JSON: ${{ steps.module.outputs.package_json }} + run: | + PKG_VERSION=$(jq -r '.version' "$PACKAGE_JSON") + if [ "$VERSION" != "$PKG_VERSION" ]; then + echo "::error::Tag version '$VERSION' does not match $PACKAGE_JSON version '$PKG_VERSION'" + exit 1 + fi + echo "Version confirmed: $PKG_VERSION" + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version-file: .nvmrc + # Required so `npm publish` writes a registry URL to .npmrc that the + # OIDC trusted-publishing flow looks for. + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: npm ci + + - name: Test + run: npm test + + - name: Dry run publish + run: npm publish --dry-run --access public + + - name: Extract changelog section for tag + env: + TAG: ${{ github.ref_name }} + CHANGELOG: ${{ steps.module.outputs.changelog }} + run: | + # Extract this tag's section from CHANGELOG.md via a sed range. + # The address `\@^## \[TAG\]@,\@^## \[@` selects from the version + # header through the next `## [` line; the inner block deletes + # both markers and prints the body. sed range patterns don't test + # the end pattern against the start line, so the start doesn't + # self-terminate. `\@...@` switches the address delimiter from + # `/` to `@` so tags containing `/` (e.g. `openfeature/v0.1.0`) + # don't conflict with the default `/`. Mirrors the deployed + # mixpanel-android extraction logic. Falls back to a placeholder + # if the section is missing or empty. + sed -n '\@^## \['"${TAG}"'\]@,\@^## \[@{\@^## \['"${TAG}"'\]@d;\@^## \[@d;p;}' "$CHANGELOG" 2>/dev/null > release_notes.md || true + if [ ! -s release_notes.md ]; then + echo "Release $TAG" > release_notes.md + fi + echo "--- release_notes.md ---" + cat release_notes.md + + - name: Create draft GitHub release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.ref_name }} + run: | + # Idempotent: if a draft release for this tag already exists, leave it alone. + if gh release view "$TAG" >/dev/null 2>&1; then + echo "GitHub release for $TAG already exists; skipping creation" + else + gh release create "$TAG" \ + --draft \ + --title "$TAG" \ + --notes-file release_notes.md + fi + + # Publish last — npm uploads are irreversible (only unpublishable within + # 72h, with restrictions). The draft GitHub release acts as the human + # gate (mirrors the Maven Central Portal's manual Publish step). + - name: Publish to npm + run: npm publish --provenance --access public + + - name: Summary + env: + MODULE: ${{ steps.module.outputs.module }} + VERSION: ${{ steps.module.outputs.version }} + PACKAGE_NAME: ${{ steps.module.outputs.package_name }} + TAG: ${{ github.ref_name }} + run: | + { + echo "## ${MODULE} ${VERSION} published" + echo "" + echo "- [npm](https://www.npmjs.com/package/${PACKAGE_NAME}/v/${VERSION})" + echo "- [Draft GitHub Release](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG})" + echo "" + echo "### Next step" + echo "Review the draft GitHub release and click **Publish release** to make it live." + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml deleted file mode 100644 index b4938be9..00000000 --- a/.github/workflows/release-tag.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Node.js CI - -on: - workflow_dispatch: - -jobs: - test_main_code: - runs-on: macos-latest - strategy: - matrix: - node-version: [18.x] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - run: npm test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 4113652d..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Create release - -on: - push: - tags: - - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 - -jobs: - build: - name: "🚀 Release" - runs-on: ubuntu-latest - steps: - - name: "Check-out" - uses: actions/checkout@v1 - - name: "Update Release CHANGELOG" - id: update-release-changelog - uses: heinrichreimer/github-changelog-generator-action@v2.2 - with: - token: ${{ secrets.GITHUB_TOKEN }} - onlyLastTag: true - stripHeaders: false - base: "CHANGELOG.md" - headerLabel: "# Changelog" - breakingLabel: '### Breaking' - enhancementLabel: '### Enhancements' - stripGeneratorNotice: true - bugsLabel: '### Fixes' - issues: false - issuesWoLabels: false - pullRequests: true - prWoLabels: true - author: false - verbose: true - - name: Commit CHANGELOG Changes - run: | - git add . - git config user.name "zihe.jia" - git config user.email "zihe.jia@mixpanel.com" - git commit -m "Update CHANGELOG" - - name: Push CHANGELOG changes - uses: ad-m/github-push-action@v0.6.0 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: master - force: true - - name: "Prepare for the Github Release" - id: generate-release-changelog - uses: heinrichreimer/github-changelog-generator-action@v2.2 - with: - token: ${{ secrets.GITHUB_TOKEN }} - output: "output.md" - headerLabel: "# Changelog" - onlyLastTag: true - stripHeaders: false - breakingLabel: '### Breaking' - enhancementLabel: '### Enhancements' - stripGeneratorNotice: true - bugsLabel: '### Fixes' - issues: false - issuesWoLabels: false - pullRequests: true - prWoLabels: true - author: false - verbose: true - - name: "🚀 Create GitHub Release" - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - body: ${{ steps.generate-release-changelog.outputs.changelog }} diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..f0549c63 --- /dev/null +++ b/.npmrc @@ -0,0 +1,5 @@ +# Prevent supply chain attacks +min-release-age=7 +# Stop `npx` from silently downloading packages not in node_modules. +# Equivalent to passing `--no` to every `npx` invocation. +yes=false diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..3c032078 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18 diff --git a/CHANGELOG.md b/CHANGELOG.md index 159edbe7..a7d2372d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # +## [v3.3.0](https://github.com/mixpanel/mixpanel-react-native/tree/v3.3.0) (2026-02-17) + +### Enhancements + +- Set server URL during initialization using MixpanelOptions [\#368](https://github.com/mixpanel/mixpanel-react-native/pull/368) + +# + +## [v3.2.2](https://github.com/mixpanel/mixpanel-react-native/tree/v3.2.2) (2026-01-17) + +# + +## [v3.2.1](https://github.com/mixpanel/mixpanel-react-native/tree/v3.2.1) (2026-01-17) + +### Enhancements + +- bump android to 8.2.5 [\#355](https://github.com/mixpanel/mixpanel-react-native/pull/355) + +# + +## [v3.2.0](https://github.com/mixpanel/mixpanel-react-native/tree/v3.2.0) (2026-01-17) + +### Enhancements + +- feat: move async-storage to peerDependencies [\#354](https://github.com/mixpanel/mixpanel-react-native/pull/354) +- Security: Update dev dependencies and fix npm packaging [\#353](https://github.com/mixpanel/mixpanel-react-native/pull/353) + +# + ## [v3.2.0-beta.3](https://github.com/mixpanel/mixpanel-react-native/tree/v3.2.0-beta.3) (2025-12-15) ### Features @@ -556,6 +585,14 @@ This major release removes all remaining calls to Mixpanel's `/decide` API endpo + + + + + + + + diff --git a/MixpanelReactNative.podspec b/MixpanelReactNative.podspec index dfc794e8..6f3e27f1 100644 --- a/MixpanelReactNative.podspec +++ b/MixpanelReactNative.podspec @@ -19,5 +19,5 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.dependency "React-Core" - s.dependency "Mixpanel-swift", '5.1.3' + s.dependency "Mixpanel-swift", '5.2.0' end diff --git a/README.md b/README.md index fc973ade..35f00304 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Mixpanel React Native Library -##### +##### _February 17, 2026_ - [v3.3.0](https://github.com/mixpanel/mixpanel-react-native/releases/tag/v3.3.0) ## Table of Contents @@ -47,7 +47,15 @@ Mixpanel's React Native SDK is a wrapper around Mixpanel’s native iOS and Andr npm install mixpanel-react-native ``` -2. Under your application's ios folder, run +2. Install AsyncStorage (required for data persistence): + +``` +npm install @react-native-async-storage/async-storage +``` + +> **Note:** Starting from v3.2.0, `@react-native-async-storage/async-storage` is a peer dependency. This allows your project to use either v1.x or v2.x, avoiding conflicts with frameworks like Expo 52+. + +3. Under your application's ios folder, run ``` pod install @@ -55,7 +63,7 @@ pod install Please note: You do not need to update your Podfile to add Mixpanel. -3. Since Xcode 12.5, there is a known swift compile issue, please refer to this **[workaround](https://github.com/mixpanel/mixpanel-react-native/issues/43#issuecomment-829599732)**. However the compile issue has been resolved in Xcode 13.2.1+, there is no extra step required as long as you upgrade to Xcode 13.2.1+. +4. Since Xcode 12.5, there is a known swift compile issue, please refer to this **[workaround](https://github.com/mixpanel/mixpanel-react-native/issues/43#issuecomment-829599732)**. However the compile issue has been resolved in Xcode 13.2.1+, there is no extra step required as long as you upgrade to Xcode 13.2.1+. ### 2. Initialize Mixpanel diff --git a/Samples/ContextAPIMixpanel/.buckconfig b/Samples/ContextAPIMixpanel/.buckconfig deleted file mode 100644 index 934256cb..00000000 --- a/Samples/ContextAPIMixpanel/.buckconfig +++ /dev/null @@ -1,6 +0,0 @@ - -[android] - target = Google Inc.:Google APIs:23 - -[maven_repositories] - central = https://repo1.maven.org/maven2 diff --git a/Samples/ContextAPIMixpanel/.editorconfig b/Samples/ContextAPIMixpanel/.editorconfig deleted file mode 100644 index 7c286132..00000000 --- a/Samples/ContextAPIMixpanel/.editorconfig +++ /dev/null @@ -1,3 +0,0 @@ -# Windows files -[*.bat] -end_of_line = crlf diff --git a/Samples/ContextAPIMixpanel/.eslintrc.js b/Samples/ContextAPIMixpanel/.eslintrc.js deleted file mode 100644 index 40c6dcd0..00000000 --- a/Samples/ContextAPIMixpanel/.eslintrc.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - root: true, - extends: '@react-native-community', -}; diff --git a/Samples/ContextAPIMixpanel/.flowconfig b/Samples/ContextAPIMixpanel/.flowconfig deleted file mode 100644 index 315f2747..00000000 --- a/Samples/ContextAPIMixpanel/.flowconfig +++ /dev/null @@ -1,66 +0,0 @@ -[ignore] -; We fork some components by platform -.*/*[.]android.js - -; Ignore "BUCK" generated dirs -/\.buckd/ - -; Ignore polyfills -node_modules/react-native/Libraries/polyfills/.* - -; Flow doesn't support platforms -.*/Libraries/Utilities/LoadingView.js - -[untyped] -.*/node_modules/@react-native-community/cli/.*/.* - -[include] - -[libs] -node_modules/react-native/interface.js -node_modules/react-native/flow/ - -[options] -emoji=true - -esproposal.optional_chaining=enable -esproposal.nullish_coalescing=enable - -exact_by_default=true - -module.file_ext=.js -module.file_ext=.json -module.file_ext=.ios.js - -munge_underscores=true - -module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1' -module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub' - -suppress_type=$FlowIssue -suppress_type=$FlowFixMe -suppress_type=$FlowFixMeProps -suppress_type=$FlowFixMeState - -[lints] -sketchy-null-number=warn -sketchy-null-mixed=warn -sketchy-number=warn -untyped-type-import=warn -nonstrict-import=warn -deprecated-type=warn -unsafe-getters-setters=warn -unnecessary-invariant=warn -signature-verification-failure=warn - -[strict] -deprecated-type -nonstrict-import -sketchy-null -unclear-type -unsafe-getters-setters -untyped-import -untyped-type-import - -[version] -^0.137.0 diff --git a/Samples/ContextAPIMixpanel/.gitattributes b/Samples/ContextAPIMixpanel/.gitattributes deleted file mode 100644 index 45a3dcb2..00000000 --- a/Samples/ContextAPIMixpanel/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -# Windows files should use crlf line endings -# https://help.github.com/articles/dealing-with-line-endings/ -*.bat text eol=crlf diff --git a/Samples/ContextAPIMixpanel/.gitignore b/Samples/ContextAPIMixpanel/.gitignore deleted file mode 100644 index b9f74108..00000000 --- a/Samples/ContextAPIMixpanel/.gitignore +++ /dev/null @@ -1,61 +0,0 @@ -# OSX -# -.DS_Store - -# Xcode -# -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.hmap -*.ipa -*.xcuserstate - -# Android/IntelliJ -# -build/ -.idea -.gradle -local.properties -*.iml - -# node.js -# -node_modules/ -npm-debug.log -yarn-error.log -yarn.lock - -# BUCK -buck-out/ -\.buckd/ -*.keystore -!debug.keystore - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the -# screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/ - -*/fastlane/report.xml -*/fastlane/Preview.html -*/fastlane/screenshots - -# Bundle artifact -*.jsbundle - -# CocoaPods -/ios/Pods/ -/ios/Podfile.lock \ No newline at end of file diff --git a/Samples/ContextAPIMixpanel/.prettierrc.js b/Samples/ContextAPIMixpanel/.prettierrc.js deleted file mode 100644 index 84196d95..00000000 --- a/Samples/ContextAPIMixpanel/.prettierrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - bracketSpacing: false, - jsxBracketSameLine: true, - singleQuote: true, - trailingComma: 'all', - arrowParens: 'avoid', -}; diff --git a/Samples/ContextAPIMixpanel/.watchmanconfig b/Samples/ContextAPIMixpanel/.watchmanconfig deleted file mode 100644 index 9e26dfee..00000000 --- a/Samples/ContextAPIMixpanel/.watchmanconfig +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/Samples/ContextAPIMixpanel/Analytics.js b/Samples/ContextAPIMixpanel/Analytics.js deleted file mode 100644 index 89306988..00000000 --- a/Samples/ContextAPIMixpanel/Analytics.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import { Mixpanel } from 'mixpanel-react-native'; - -const MixpanelContext = React.createContext(); - -export const useMixpanel = () => React.useContext(MixpanelContext); - -export const MixpanelProvider = ({children}) => { - const [mixpanel, setMixpanel] = React.useState(null); - - React.useEffect(() => { - const trackAutomaticEvents = true; - const mixpanelInstance = new Mixpanel(`Your Project Token`, trackAutomaticEvents); - mixpanelInstance.init(); - setMixpanel(mixpanelInstance); - }, []); - - return {children}; -}; \ No newline at end of file diff --git a/Samples/ContextAPIMixpanel/App.js b/Samples/ContextAPIMixpanel/App.js deleted file mode 100644 index 4cabd005..00000000 --- a/Samples/ContextAPIMixpanel/App.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { SafeAreaView } from "react-native"; -import { MixpanelProvider } from './Analytics'; -import { SampleScreen } from './Screens/SampleScreen'; - - -const App = () => { - return ( - - - - - - ) -} - -export default App; \ No newline at end of file diff --git a/Samples/ContextAPIMixpanel/Screens/SampleScreen.js b/Samples/ContextAPIMixpanel/Screens/SampleScreen.js deleted file mode 100644 index ca17ad55..00000000 --- a/Samples/ContextAPIMixpanel/Screens/SampleScreen.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { Button, SafeAreaView } from "react-native"; -import { useMixpanel } from '../Analytics'; - -export const SampleScreen = () => { - const mixpanel = useMixpanel(); - return ( - -