mixpanel-ruby 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +14 -0
- data/.github/modules.json +18 -0
- data/.github/scripts/generate-changelog.sh +87 -0
- data/.github/workflows/pr-title-check.yml +51 -0
- data/.github/workflows/prepare-release.yml +189 -0
- data/.github/workflows/release-rubygems.yml +217 -0
- data/CHANGELOG.md +30 -0
- data/Readme.rdoc +65 -0
- data/lib/mixpanel-ruby/consumer.rb +54 -6
- data/lib/mixpanel-ruby/credentials.rb +26 -0
- data/lib/mixpanel-ruby/events.rb +69 -11
- data/lib/mixpanel-ruby/flags/flags_provider.rb +16 -2
- data/lib/mixpanel-ruby/flags/local_flags_provider.rb +85 -18
- data/lib/mixpanel-ruby/flags/remote_flags_provider.rb +4 -2
- data/lib/mixpanel-ruby/tracker.rb +39 -9
- data/lib/mixpanel-ruby/version.rb +1 -1
- data/lib/mixpanel-ruby.rb +1 -0
- data/openfeature-provider/CHANGELOG.md +5 -0
- data/openfeature-provider/README.md +2 -0
- data/openfeature-provider/lib/mixpanel/openfeature/version.rb +7 -0
- data/openfeature-provider/lib/mixpanel/openfeature.rb +1 -0
- data/openfeature-provider/mixpanel-ruby-openfeature.gemspec +3 -1
- data/spec/mixpanel-ruby/consumer_spec.rb +81 -0
- data/spec/mixpanel-ruby/credentials_security_spec.rb +108 -0
- data/spec/mixpanel-ruby/credentials_spec.rb +54 -0
- data/spec/mixpanel-ruby/events_spec.rb +152 -0
- data/spec/mixpanel-ruby/flags/local_flags_spec.rb +209 -2
- data/spec/mixpanel-ruby/flags/remote_flags_spec.rb +36 -0
- data/spec/mixpanel-ruby/tracker_spec.rb +18 -0
- metadata +14 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6e8271da642afa8ee7d980b2508db17f84f342f33405bde9dd6d0a15b8a10df0
|
|
4
|
+
data.tar.gz: 2d2da06b34ee942c35286b155bc5942a3ee56d8f17727063ebec16e5eb8d2149
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0f8236551ad44db0c9b3afdcefa17083bbca02ff00b6b9a236123857eaf38595070b258c923671b0f0672c76aa6baadefc7d169e02f27ec33cb475b4b9eabb70
|
|
7
|
+
data.tar.gz: 469d8373977e082acc887a8e11d8eeb973190a3488f40d5bb6fa66f4be7cd5c6e83772923e3ee291641f7aa25d8b0dd6d6123541f2e2ed90677c3b34f0e48bba
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "bundler"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
cooldown:
|
|
8
|
+
default-days: 30
|
|
9
|
+
- package-ecosystem: "github-actions"
|
|
10
|
+
directory: "/"
|
|
11
|
+
schedule:
|
|
12
|
+
interval: "weekly"
|
|
13
|
+
cooldown:
|
|
14
|
+
default-days: 30
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"analytics": {
|
|
3
|
+
"tag_prefix": "v",
|
|
4
|
+
"gemspec": "mixpanel-ruby.gemspec",
|
|
5
|
+
"version_file": "lib/mixpanel-ruby/version.rb",
|
|
6
|
+
"changelog": "CHANGELOG.md",
|
|
7
|
+
"readme": "Readme.rdoc",
|
|
8
|
+
"package_name": "mixpanel-ruby"
|
|
9
|
+
},
|
|
10
|
+
"openfeature": {
|
|
11
|
+
"tag_prefix": "openfeature/v",
|
|
12
|
+
"gemspec": "openfeature-provider/mixpanel-ruby-openfeature.gemspec",
|
|
13
|
+
"version_file": "openfeature-provider/lib/mixpanel/openfeature/version.rb",
|
|
14
|
+
"changelog": "openfeature-provider/CHANGELOG.md",
|
|
15
|
+
"readme": "openfeature-provider/README.md",
|
|
16
|
+
"package_name": "mixpanel-ruby-openfeature"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
MODULE="$1"
|
|
5
|
+
VERSION_LABEL="$2"
|
|
6
|
+
REPO_URL="$3"
|
|
7
|
+
END_REF="${4:-HEAD}"
|
|
8
|
+
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
|
+
MODULES_JSON="$SCRIPT_DIR/../modules.json"
|
|
11
|
+
|
|
12
|
+
TAG_PREFIX=$(jq -e -r --arg m "$MODULE" '.[$m].tag_prefix' "$MODULES_JSON") || {
|
|
13
|
+
echo "Unknown module: $MODULE. Valid modules: $(jq -r 'keys | join(", ")' "$MODULES_JSON")" >&2
|
|
14
|
+
exit 1
|
|
15
|
+
}
|
|
16
|
+
TAG_GLOB="${TAG_PREFIX}*"
|
|
17
|
+
|
|
18
|
+
PREVIOUS_TAG=$(git tag --sort=-creatordate --list "$TAG_GLOB" | head -1 || true)
|
|
19
|
+
|
|
20
|
+
if [ -z "$PREVIOUS_TAG" ]; then
|
|
21
|
+
RANGE="$END_REF"
|
|
22
|
+
else
|
|
23
|
+
RANGE="${PREVIOUS_TAG}..${END_REF}"
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
DATE=$(date +%Y-%m-%d)
|
|
27
|
+
SAFE_URL=$(printf '%s' "$REPO_URL" | sed 's|[&/\]|\\&|g')
|
|
28
|
+
|
|
29
|
+
declare -a FEATURES=()
|
|
30
|
+
declare -a FIXES=()
|
|
31
|
+
declare -a CHORES=()
|
|
32
|
+
|
|
33
|
+
while IFS= read -r line; do
|
|
34
|
+
[ -z "$line" ] && continue
|
|
35
|
+
MSG=$(echo "$line" | cut -d' ' -f2-)
|
|
36
|
+
|
|
37
|
+
# feat / fix: include whether bare, scoped to our module, or scoped to
|
|
38
|
+
# `all` (cross-cutting changes that appear in every module's changelog).
|
|
39
|
+
# chore: include only when explicitly scoped to our module or `all` —
|
|
40
|
+
# bare `chore:` is the convention for changes intentionally hidden from
|
|
41
|
+
# the changelog (release prep PRs, CI tweaks, lockfile bumps, internal
|
|
42
|
+
# docs).
|
|
43
|
+
if [[ "$MSG" =~ ^(feat|fix)(\((${MODULE}|all)\))?:\ (.+) ]]; then
|
|
44
|
+
TYPE="${BASH_REMATCH[1]}"
|
|
45
|
+
DESC="${BASH_REMATCH[4]}"
|
|
46
|
+
DESC=$(echo "$DESC" | sed -E "s|\(#([0-9]+)\)|([#\1](${SAFE_URL}/pull/\1))|g")
|
|
47
|
+
case "$TYPE" in
|
|
48
|
+
feat) FEATURES+=("$DESC") ;;
|
|
49
|
+
fix) FIXES+=("$DESC") ;;
|
|
50
|
+
esac
|
|
51
|
+
elif [[ "$MSG" =~ ^chore\((${MODULE}|all)\):\ (.+) ]]; then
|
|
52
|
+
DESC="${BASH_REMATCH[2]}"
|
|
53
|
+
DESC=$(echo "$DESC" | sed -E "s|\(#([0-9]+)\)|([#\1](${SAFE_URL}/pull/\1))|g")
|
|
54
|
+
CHORES+=("$DESC")
|
|
55
|
+
fi
|
|
56
|
+
done < <(git log --oneline "$RANGE")
|
|
57
|
+
|
|
58
|
+
echo "## [${VERSION_LABEL}](${REPO_URL}/tree/${VERSION_LABEL}) (${DATE})"
|
|
59
|
+
echo ""
|
|
60
|
+
|
|
61
|
+
if [ ${#FEATURES[@]} -gt 0 ]; then
|
|
62
|
+
echo "### Features"
|
|
63
|
+
for entry in "${FEATURES[@]}"; do
|
|
64
|
+
echo "- ${entry}"
|
|
65
|
+
done
|
|
66
|
+
echo ""
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
if [ ${#FIXES[@]} -gt 0 ]; then
|
|
70
|
+
echo "### Fixes"
|
|
71
|
+
for entry in "${FIXES[@]}"; do
|
|
72
|
+
echo "- ${entry}"
|
|
73
|
+
done
|
|
74
|
+
echo ""
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
if [ ${#CHORES[@]} -gt 0 ]; then
|
|
78
|
+
echo "### Chores"
|
|
79
|
+
for entry in "${CHORES[@]}"; do
|
|
80
|
+
echo "- ${entry}"
|
|
81
|
+
done
|
|
82
|
+
echo ""
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
if [ -n "$PREVIOUS_TAG" ]; then
|
|
86
|
+
echo "[Full Changelog](${REPO_URL}/compare/${PREVIOUS_TAG}...${VERSION_LABEL})"
|
|
87
|
+
fi
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: PR Title Check
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, edited, synchronize, reopened]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
check-title:
|
|
12
|
+
name: Validate PR title
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout
|
|
16
|
+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
17
|
+
with:
|
|
18
|
+
sparse-checkout: .github/modules.json
|
|
19
|
+
sparse-checkout-cone-mode: false
|
|
20
|
+
|
|
21
|
+
- name: Check PR title format
|
|
22
|
+
env:
|
|
23
|
+
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
24
|
+
run: |
|
|
25
|
+
MODULE_LIST=$(jq -r 'keys | join("|")' .github/modules.json)
|
|
26
|
+
# Scope is optional. Bare, scoped to a known module, or scoped to
|
|
27
|
+
# `all` (cross-cutting changes that appear in every module's
|
|
28
|
+
# changelog) all pass.
|
|
29
|
+
MAIN_PATTERN="^(feat|fix|chore)(\((${MODULE_LIST}|all)\))?: .+"
|
|
30
|
+
RELEASE_PATTERN="^release: .+"
|
|
31
|
+
|
|
32
|
+
if [[ "$PR_TITLE" =~ $MAIN_PATTERN ]] || [[ "$PR_TITLE" =~ $RELEASE_PATTERN ]]; then
|
|
33
|
+
echo "PR title is valid: $PR_TITLE"
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
echo "PR title does not match the required format."
|
|
38
|
+
echo ""
|
|
39
|
+
echo " Got: $PR_TITLE"
|
|
40
|
+
echo ""
|
|
41
|
+
echo "Expected one of:"
|
|
42
|
+
echo " feat: description"
|
|
43
|
+
echo " fix: description"
|
|
44
|
+
echo " chore: description"
|
|
45
|
+
echo " feat(<module>|all): description"
|
|
46
|
+
echo " fix(<module>|all): description"
|
|
47
|
+
echo " chore(<module>|all): description"
|
|
48
|
+
echo " release: description"
|
|
49
|
+
echo ""
|
|
50
|
+
echo "Valid scopes: ${MODULE_LIST//|/, }, all"
|
|
51
|
+
exit 1
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
name: Prepare Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
module:
|
|
7
|
+
description: 'Module to release (must match a key in .github/modules.json)'
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
version:
|
|
11
|
+
description: 'Release version (e.g., 3.2.0 or 0.2.0)'
|
|
12
|
+
required: true
|
|
13
|
+
type: string
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: write
|
|
17
|
+
pull-requests: write
|
|
18
|
+
|
|
19
|
+
concurrency:
|
|
20
|
+
group: prepare-release-${{ inputs.module }}
|
|
21
|
+
cancel-in-progress: false
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
prepare:
|
|
25
|
+
name: "Prepare ${{ inputs.module }} ${{ inputs.version }}"
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
steps:
|
|
28
|
+
- name: Checkout
|
|
29
|
+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 0
|
|
32
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
33
|
+
|
|
34
|
+
- name: Validate inputs
|
|
35
|
+
env:
|
|
36
|
+
MODULE: ${{ inputs.module }}
|
|
37
|
+
VERSION: ${{ inputs.version }}
|
|
38
|
+
run: |
|
|
39
|
+
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
|
|
40
|
+
echo "::error::Invalid version format: $VERSION"
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
jq -e --arg m "$MODULE" '.[$m]' .github/modules.json > /dev/null || {
|
|
44
|
+
echo "::error::Unknown module '$MODULE'. Valid modules: $(jq -r 'keys | join(", ")' .github/modules.json)"
|
|
45
|
+
exit 1
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
- name: Resolve module config
|
|
49
|
+
id: config
|
|
50
|
+
env:
|
|
51
|
+
MODULE: ${{ inputs.module }}
|
|
52
|
+
VERSION: ${{ inputs.version }}
|
|
53
|
+
run: |
|
|
54
|
+
MODULE_CONFIG=$(jq -e --arg m "$MODULE" '.[$m]' .github/modules.json)
|
|
55
|
+
|
|
56
|
+
TAG_PREFIX=$(echo "$MODULE_CONFIG" | jq -r '.tag_prefix')
|
|
57
|
+
{
|
|
58
|
+
echo "tag=${TAG_PREFIX}${VERSION}"
|
|
59
|
+
echo "tag_prefix=${TAG_PREFIX}"
|
|
60
|
+
echo "gemspec=$(echo "$MODULE_CONFIG" | jq -r '.gemspec')"
|
|
61
|
+
echo "version_file=$(echo "$MODULE_CONFIG" | jq -r '.version_file')"
|
|
62
|
+
echo "changelog=$(echo "$MODULE_CONFIG" | jq -r '.changelog')"
|
|
63
|
+
echo "readme=$(echo "$MODULE_CONFIG" | jq -r '.readme')"
|
|
64
|
+
echo "package_name=$(echo "$MODULE_CONFIG" | jq -r '.package_name')"
|
|
65
|
+
echo "branch=release/${MODULE}/${VERSION}"
|
|
66
|
+
} >> "$GITHUB_OUTPUT"
|
|
67
|
+
|
|
68
|
+
- name: Validate version not already released
|
|
69
|
+
env:
|
|
70
|
+
TAG: ${{ steps.config.outputs.tag }}
|
|
71
|
+
run: |
|
|
72
|
+
if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then
|
|
73
|
+
echo "::error::Tag $TAG already exists on origin"
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
- name: Clean up existing release branch and PR
|
|
78
|
+
env:
|
|
79
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
80
|
+
BRANCH: ${{ steps.config.outputs.branch }}
|
|
81
|
+
run: |
|
|
82
|
+
EXISTING_PR=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || true)
|
|
83
|
+
if [[ -n "$EXISTING_PR" ]]; then
|
|
84
|
+
echo "Closing existing PR #$EXISTING_PR and deleting branch"
|
|
85
|
+
gh pr close "$EXISTING_PR" --delete-branch
|
|
86
|
+
elif git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then
|
|
87
|
+
echo "Deleting orphaned branch $BRANCH"
|
|
88
|
+
git push origin --delete "$BRANCH"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
- name: Create release branch
|
|
92
|
+
env:
|
|
93
|
+
BRANCH: ${{ steps.config.outputs.branch }}
|
|
94
|
+
run: git checkout -b "$BRANCH"
|
|
95
|
+
|
|
96
|
+
- name: Bump version in version.rb
|
|
97
|
+
env:
|
|
98
|
+
VERSION: ${{ inputs.version }}
|
|
99
|
+
VERSION_FILE: ${{ steps.config.outputs.version_file }}
|
|
100
|
+
run: |
|
|
101
|
+
sed -i -E "s/VERSION = .*/VERSION = '${VERSION}'/" "$VERSION_FILE"
|
|
102
|
+
echo "Updated $VERSION_FILE:"
|
|
103
|
+
grep -n "VERSION" "$VERSION_FILE" || true
|
|
104
|
+
|
|
105
|
+
- name: Update README version header
|
|
106
|
+
env:
|
|
107
|
+
REPO_URL: ${{ github.server_url }}/${{ github.repository }}
|
|
108
|
+
TAG: ${{ steps.config.outputs.tag }}
|
|
109
|
+
README: ${{ steps.config.outputs.readme }}
|
|
110
|
+
run: |
|
|
111
|
+
DATE=$(date +"%B %d, %Y")
|
|
112
|
+
# Replace the first matching version-header line. The 1,/pat/ address
|
|
113
|
+
# range bounds the substitution so multi-version READMEs aren't
|
|
114
|
+
# trampled. Works for both Markdown and rdoc files (rdoc passes
|
|
115
|
+
# unrecognized lines through as text).
|
|
116
|
+
sed -i -E \
|
|
117
|
+
"1,/^##### _.*_ - \[.*\]\(.*\)\$/ s|^##### _.*_ - \[.*\]\(.*\)\$|##### _${DATE}_ - [${TAG}](${REPO_URL}/releases/tag/${TAG})|" \
|
|
118
|
+
"$README"
|
|
119
|
+
|
|
120
|
+
- name: Generate changelog
|
|
121
|
+
env:
|
|
122
|
+
REPO_URL: ${{ github.server_url }}/${{ github.repository }}
|
|
123
|
+
MODULE: ${{ inputs.module }}
|
|
124
|
+
TAG: ${{ steps.config.outputs.tag }}
|
|
125
|
+
CHANGELOG_FILE: ${{ steps.config.outputs.changelog }}
|
|
126
|
+
run: |
|
|
127
|
+
CHANGELOG=$(.github/scripts/generate-changelog.sh \
|
|
128
|
+
"$MODULE" "$TAG" "$REPO_URL" HEAD)
|
|
129
|
+
|
|
130
|
+
if [ -f "$CHANGELOG_FILE" ]; then
|
|
131
|
+
{
|
|
132
|
+
printf '# Changelog\n\n%s\n' "$CHANGELOG"
|
|
133
|
+
sed '1{/^# Changelog$/d;}' "$CHANGELOG_FILE"
|
|
134
|
+
} > CHANGELOG.new.md
|
|
135
|
+
mv CHANGELOG.new.md "$CHANGELOG_FILE"
|
|
136
|
+
else
|
|
137
|
+
printf '# Changelog\n\n%s\n' "$CHANGELOG" > "$CHANGELOG_FILE"
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
- name: Commit and push
|
|
141
|
+
env:
|
|
142
|
+
MODULE: ${{ inputs.module }}
|
|
143
|
+
VERSION: ${{ inputs.version }}
|
|
144
|
+
BRANCH: ${{ steps.config.outputs.branch }}
|
|
145
|
+
VERSION_FILE: ${{ steps.config.outputs.version_file }}
|
|
146
|
+
CHANGELOG_FILE: ${{ steps.config.outputs.changelog }}
|
|
147
|
+
README: ${{ steps.config.outputs.readme }}
|
|
148
|
+
run: |
|
|
149
|
+
git config user.name "github-actions[bot]"
|
|
150
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
151
|
+
git add "$VERSION_FILE" "$CHANGELOG_FILE" "$README"
|
|
152
|
+
git commit -m "release: prepare ${MODULE} ${VERSION}"
|
|
153
|
+
git push origin "$BRANCH"
|
|
154
|
+
|
|
155
|
+
- name: Create pull request
|
|
156
|
+
env:
|
|
157
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
158
|
+
MODULE: ${{ inputs.module }}
|
|
159
|
+
VERSION: ${{ inputs.version }}
|
|
160
|
+
TAG: ${{ steps.config.outputs.tag }}
|
|
161
|
+
VERSION_FILE: ${{ steps.config.outputs.version_file }}
|
|
162
|
+
CHANGELOG_FILE: ${{ steps.config.outputs.changelog }}
|
|
163
|
+
README: ${{ steps.config.outputs.readme }}
|
|
164
|
+
BRANCH: ${{ steps.config.outputs.branch }}
|
|
165
|
+
run: |
|
|
166
|
+
gh pr create \
|
|
167
|
+
--title "release: prepare ${MODULE} ${VERSION}" \
|
|
168
|
+
--body "$(cat <<EOF
|
|
169
|
+
## Release ${MODULE} ${VERSION}
|
|
170
|
+
|
|
171
|
+
This PR prepares the ${MODULE} module for release.
|
|
172
|
+
|
|
173
|
+
### Changes
|
|
174
|
+
- Bumps \`VERSION\` to \`${VERSION}\` in \`${VERSION_FILE}\`
|
|
175
|
+
- Updates \`${CHANGELOG_FILE}\` with a new section since the last \`${TAG%${VERSION}}*\` tag
|
|
176
|
+
- Updates \`${README}\` version header
|
|
177
|
+
|
|
178
|
+
### After merging
|
|
179
|
+
1. Push tag \`${TAG}\` from the merge commit on \`master\` to trigger the publish workflow:
|
|
180
|
+
\`\`\`
|
|
181
|
+
git checkout master && git pull
|
|
182
|
+
git tag ${TAG}
|
|
183
|
+
git push origin ${TAG}
|
|
184
|
+
\`\`\`
|
|
185
|
+
2. The publish workflow creates a draft GitHub release and publishes to RubyGems via OIDC trusted publishing. Review and publish the GitHub release after the workflow finishes.
|
|
186
|
+
EOF
|
|
187
|
+
)" \
|
|
188
|
+
--base master \
|
|
189
|
+
--head "$BRANCH"
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
name: Publish Release to RubyGems
|
|
2
|
+
|
|
3
|
+
# Tag-triggered: RubyGems trusted publishing requires the publish to come from
|
|
4
|
+
# a workflow run triggered by a tag-push matching the pattern configured in
|
|
5
|
+
# the gem's RubyGems "Trusted Publishers" entry. Add new tag prefixes here as
|
|
6
|
+
# additional modules are added to .github/modules.json.
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags:
|
|
10
|
+
- 'v*'
|
|
11
|
+
- 'openfeature/v*'
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: rubygems-publish
|
|
15
|
+
cancel-in-progress: false
|
|
16
|
+
|
|
17
|
+
permissions:
|
|
18
|
+
contents: write # create the draft GitHub release
|
|
19
|
+
id-token: write # OIDC for RubyGems trusted publishing
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
publish:
|
|
23
|
+
name: "Publish ${{ github.ref_name }} to RubyGems"
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
environment: release
|
|
26
|
+
timeout-minutes: 30
|
|
27
|
+
|
|
28
|
+
# One-time RubyGems / GitHub Environment / tag ruleset setup is required.
|
|
29
|
+
# The `environment:` name above must match what RubyGems' Trusted Publishers
|
|
30
|
+
# entry is configured to require.
|
|
31
|
+
|
|
32
|
+
steps:
|
|
33
|
+
- name: Checkout tagged commit
|
|
34
|
+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
35
|
+
with:
|
|
36
|
+
fetch-depth: 0
|
|
37
|
+
|
|
38
|
+
- name: Resolve module from tag
|
|
39
|
+
id: module
|
|
40
|
+
env:
|
|
41
|
+
TAG: ${{ github.ref_name }}
|
|
42
|
+
run: |
|
|
43
|
+
MODULE=$(jq -r --arg t "$TAG" '
|
|
44
|
+
to_entries
|
|
45
|
+
| map(select(.value.tag_prefix as $p | $t | startswith($p)))
|
|
46
|
+
| max_by(.value.tag_prefix | length)
|
|
47
|
+
| .key // empty
|
|
48
|
+
' .github/modules.json)
|
|
49
|
+
|
|
50
|
+
if [ -z "$MODULE" ]; then
|
|
51
|
+
echo "::error::Tag '$TAG' does not match any module tag_prefix in .github/modules.json"
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
MODULE_CONFIG=$(jq -e --arg m "$MODULE" '.[$m]' .github/modules.json)
|
|
56
|
+
TAG_PREFIX=$(echo "$MODULE_CONFIG" | jq -r '.tag_prefix')
|
|
57
|
+
VERSION="${TAG#${TAG_PREFIX}}"
|
|
58
|
+
|
|
59
|
+
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
|
|
60
|
+
echo "::error::Version '$VERSION' (from tag '$TAG') is not a valid semver"
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
{
|
|
65
|
+
echo "module=$MODULE"
|
|
66
|
+
echo "version=$VERSION"
|
|
67
|
+
echo "tag_prefix=$TAG_PREFIX"
|
|
68
|
+
echo "gemspec=$(echo "$MODULE_CONFIG" | jq -r '.gemspec')"
|
|
69
|
+
echo "version_file=$(echo "$MODULE_CONFIG" | jq -r '.version_file')"
|
|
70
|
+
echo "changelog=$(echo "$MODULE_CONFIG" | jq -r '.changelog')"
|
|
71
|
+
echo "package_name=$(echo "$MODULE_CONFIG" | jq -r '.package_name')"
|
|
72
|
+
} >> "$GITHUB_OUTPUT"
|
|
73
|
+
|
|
74
|
+
echo "Resolved tag '$TAG' -> module '$MODULE', version '$VERSION'"
|
|
75
|
+
|
|
76
|
+
- name: Verify tag commit is on master
|
|
77
|
+
env:
|
|
78
|
+
TAG: ${{ github.ref_name }}
|
|
79
|
+
run: |
|
|
80
|
+
git fetch origin master
|
|
81
|
+
TAG_SHA=$(git rev-parse HEAD)
|
|
82
|
+
if ! git merge-base --is-ancestor "$TAG_SHA" origin/master; then
|
|
83
|
+
echo "::error::Tag '$TAG' ($TAG_SHA) is not an ancestor of origin/master."
|
|
84
|
+
echo "Tags must be pushed from a commit on the master branch."
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
- name: Validate version_file matches tag
|
|
89
|
+
env:
|
|
90
|
+
VERSION: ${{ steps.module.outputs.version }}
|
|
91
|
+
VERSION_FILE: ${{ steps.module.outputs.version_file }}
|
|
92
|
+
run: |
|
|
93
|
+
FILE_VERSION=$(grep -E "^[[:space:]]*VERSION[[:space:]]*=" "$VERSION_FILE" \
|
|
94
|
+
| head -1 \
|
|
95
|
+
| sed -E "s/.*VERSION[[:space:]]*=[[:space:]]*['\"]([^'\"]+)['\"].*/\1/")
|
|
96
|
+
if [ -z "$FILE_VERSION" ]; then
|
|
97
|
+
echo "::error::Could not parse VERSION from $VERSION_FILE"
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
if [ "$VERSION" != "$FILE_VERSION" ]; then
|
|
101
|
+
echo "::error::Tag version '$VERSION' does not match $VERSION_FILE version '$FILE_VERSION'"
|
|
102
|
+
exit 1
|
|
103
|
+
fi
|
|
104
|
+
echo "Version confirmed: $FILE_VERSION"
|
|
105
|
+
|
|
106
|
+
- name: Set up Ruby
|
|
107
|
+
uses: ruby/setup-ruby@d697be2f83c6234b20877c3b5eac7a7f342f0d0c # v1.269.0
|
|
108
|
+
with:
|
|
109
|
+
ruby-version: '3.3'
|
|
110
|
+
bundler-cache: false
|
|
111
|
+
|
|
112
|
+
- name: Install dependencies
|
|
113
|
+
env:
|
|
114
|
+
GEMSPEC: ${{ steps.module.outputs.gemspec }}
|
|
115
|
+
run: |
|
|
116
|
+
GEM_DIR=$(dirname "$GEMSPEC")
|
|
117
|
+
if [ -f "$GEM_DIR/Gemfile" ]; then
|
|
118
|
+
(cd "$GEM_DIR" && bundle install)
|
|
119
|
+
else
|
|
120
|
+
bundle install
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
- name: Run tests
|
|
124
|
+
env:
|
|
125
|
+
MODULE: ${{ steps.module.outputs.module }}
|
|
126
|
+
GEMSPEC: ${{ steps.module.outputs.gemspec }}
|
|
127
|
+
run: |
|
|
128
|
+
GEM_DIR=$(dirname "$GEMSPEC")
|
|
129
|
+
if [ -f "$GEM_DIR/Gemfile" ] && [ "$GEM_DIR" != "." ]; then
|
|
130
|
+
(cd "$GEM_DIR" && bundle exec rspec)
|
|
131
|
+
else
|
|
132
|
+
bundle exec rake
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
- name: Build gem
|
|
136
|
+
id: build
|
|
137
|
+
env:
|
|
138
|
+
GEMSPEC: ${{ steps.module.outputs.gemspec }}
|
|
139
|
+
run: |
|
|
140
|
+
GEM_DIR=$(dirname "$GEMSPEC")
|
|
141
|
+
GEMSPEC_BASENAME=$(basename "$GEMSPEC")
|
|
142
|
+
if [ "$GEM_DIR" = "." ]; then
|
|
143
|
+
gem build "$GEMSPEC_BASENAME"
|
|
144
|
+
GEM_FILE=$(ls -t *.gem | head -1)
|
|
145
|
+
echo "gem_path=${GEM_FILE}" >> "$GITHUB_OUTPUT"
|
|
146
|
+
else
|
|
147
|
+
(cd "$GEM_DIR" && gem build "$GEMSPEC_BASENAME")
|
|
148
|
+
GEM_FILE=$(ls -t "$GEM_DIR"/*.gem | head -1)
|
|
149
|
+
echo "gem_path=${GEM_FILE}" >> "$GITHUB_OUTPUT"
|
|
150
|
+
fi
|
|
151
|
+
echo "Built: $(ls -la "${GEM_FILE}")"
|
|
152
|
+
|
|
153
|
+
- name: Extract changelog section for tag
|
|
154
|
+
env:
|
|
155
|
+
TAG: ${{ github.ref_name }}
|
|
156
|
+
CHANGELOG: ${{ steps.module.outputs.changelog }}
|
|
157
|
+
run: |
|
|
158
|
+
# Extract this tag's section from CHANGELOG.md via a sed range.
|
|
159
|
+
# The address `\@^## \[TAG\]@,\@^## \[@` selects from the version
|
|
160
|
+
# header through the next `## [` line; the inner block deletes
|
|
161
|
+
# both markers and prints the body. sed range patterns don't test
|
|
162
|
+
# the end pattern against the start line, so the start doesn't
|
|
163
|
+
# self-terminate. `\@...@` switches the address delimiter from
|
|
164
|
+
# `/` to `@` so tags containing `/` (e.g. `openfeature/v0.1.0`)
|
|
165
|
+
# don't conflict with the default `/`. Mirrors the deployed
|
|
166
|
+
# mixpanel-android extraction logic. Falls back to a placeholder
|
|
167
|
+
# if the section is missing or empty.
|
|
168
|
+
sed -n '\@^## \['"${TAG}"'\]@,\@^## \[@{\@^## \['"${TAG}"'\]@d;\@^## \[@d;p;}' "$CHANGELOG" 2>/dev/null > release_notes.md || true
|
|
169
|
+
if [ ! -s release_notes.md ]; then
|
|
170
|
+
echo "Release $TAG" > release_notes.md
|
|
171
|
+
fi
|
|
172
|
+
echo "--- release_notes.md ---"
|
|
173
|
+
cat release_notes.md
|
|
174
|
+
|
|
175
|
+
- name: Create draft GitHub release
|
|
176
|
+
env:
|
|
177
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
178
|
+
TAG: ${{ github.ref_name }}
|
|
179
|
+
run: |
|
|
180
|
+
# Idempotent: if a draft release for this tag already exists, leave it alone.
|
|
181
|
+
if gh release view "$TAG" >/dev/null 2>&1; then
|
|
182
|
+
echo "GitHub release for $TAG already exists; skipping creation"
|
|
183
|
+
else
|
|
184
|
+
gh release create "$TAG" \
|
|
185
|
+
--draft \
|
|
186
|
+
--title "$TAG" \
|
|
187
|
+
--notes-file release_notes.md
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
- name: Configure RubyGems trusted publishing credentials
|
|
191
|
+
uses: rubygems/configure-rubygems-credentials@762a4b77c3300434bb57c7ce80b20e36231927aa # v2.0.0
|
|
192
|
+
|
|
193
|
+
# Publish last - RubyGems uploads are effectively irreversible (yank is
|
|
194
|
+
# restricted). The draft GitHub release + `release` environment reviewer
|
|
195
|
+
# acts as the human gate.
|
|
196
|
+
- name: Publish gem to RubyGems
|
|
197
|
+
env:
|
|
198
|
+
GEM_PATH: ${{ steps.build.outputs.gem_path }}
|
|
199
|
+
run: |
|
|
200
|
+
gem push "$GEM_PATH"
|
|
201
|
+
|
|
202
|
+
- name: Summary
|
|
203
|
+
env:
|
|
204
|
+
MODULE: ${{ steps.module.outputs.module }}
|
|
205
|
+
VERSION: ${{ steps.module.outputs.version }}
|
|
206
|
+
PACKAGE_NAME: ${{ steps.module.outputs.package_name }}
|
|
207
|
+
TAG: ${{ github.ref_name }}
|
|
208
|
+
run: |
|
|
209
|
+
{
|
|
210
|
+
echo "## ${MODULE} ${VERSION} published"
|
|
211
|
+
echo ""
|
|
212
|
+
echo "- [RubyGems](https://rubygems.org/gems/${PACKAGE_NAME}/versions/${VERSION})"
|
|
213
|
+
echo "- [Draft GitHub Release](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG})"
|
|
214
|
+
echo ""
|
|
215
|
+
echo "### Next step"
|
|
216
|
+
echo "Review the draft GitHub release and click **Publish release** to make it live."
|
|
217
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [v3.2.0](https://github.com/mixpanel/mixpanel-ruby/tree/v3.2.0) (2026-07-10)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
- add service account support ([#152](https://github.com/mixpanel/mixpanel-ruby/pull/152))
|
|
7
|
+
|
|
8
|
+
### Fixes
|
|
9
|
+
- Make local-flags polling loop shutdown promptly ([#149](https://github.com/mixpanel/mixpanel-ruby/pull/149))
|
|
10
|
+
|
|
11
|
+
[Full Changelog](https://github.com/mixpanel/mixpanel-ruby/compare/v3.1.0...v3.2.0)
|
|
12
|
+
|
|
13
|
+
## [Unreleased]
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Service account authentication support via `ServiceAccountCredentials` class
|
|
18
|
+
- `Tracker.new()`, `Events.new()`, `Consumer.new()`, and `BufferedConsumer.new()` now accept `credentials:` parameter
|
|
19
|
+
- Credentials are passed to feature flags providers (local and remote) for authenticated API access
|
|
20
|
+
- Authentication validation: `import()` now raises `ArgumentError` when called without either `api_key` or `credentials`
|
|
21
|
+
- Comprehensive test coverage for credential handling and security in `spec/mixpanel-ruby/credentials_spec.rb` and `spec/mixpanel-ruby/credentials_security_spec.rb`
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- When using service account credentials, pass `nil` as the first parameter to `import()`: `tracker.import(nil, distinct_id, event, properties)` instead of passing an API key
|
|
26
|
+
- Credentials are stored as instance variables and used only for HTTP Basic Auth headers, never serialized to JSON payloads
|
|
27
|
+
|
|
28
|
+
## [v3.1.0](https://github.com/mixpanel/mixpanel-ruby/tree/v3.1.0) (2026-05-13)
|
|
29
|
+
|
|
30
|
+
Initial entry for the standardized release process. See `Readme.rdoc` for prior version history.
|