Skip to content

Conversation

@graycreate
Copy link
Member

Summary

  • Create CHANGELOG.md with history from git (v2.3.10 - v2.3.14)
  • Add fastlane/changelog_helper.rb for changelog extraction and validation
  • Modify release.yml to validate changelog exists before release
  • Update release.yml to generate GitHub Release notes from CHANGELOG.md
  • Update release.yml to generate Google Play whatsnew from CHANGELOG.md
  • Add validate_changelog and version_info lanes to Fastfile
  • Add changelog validation to deploy_beta and deploy_production lanes

Motivation

This aligns with the iOS version's changelog workflow where:

  • Developers manually maintain CHANGELOG.md with version entries
  • Release will fail if no changelog entry exists for current version
  • Release notes are automatically extracted from CHANGELOG.md

New Workflow

When releasing a new version:

  1. Update version in config.gradle
  2. Add new version entry at the top of CHANGELOG.md
  3. Commit and push to main
  4. Release workflow validates changelog exists (fails if missing)
  5. GitHub Release and Google Play notes generated from CHANGELOG.md

Files Changed

File Description
CHANGELOG.md New changelog file with version history
fastlane/changelog_helper.rb Ruby helper for changelog extraction
.github/workflows/release.yml Updated to use CHANGELOG.md
fastlane/Fastfile Added changelog validation lanes

Test plan

  • Verify fastlane validate_changelog works correctly
  • Verify fastlane version_info displays changelog
  • Test release workflow validates changelog entry
  • Test GitHub Release notes are generated correctly
  • Test Google Play whatsnew is generated correctly

🤖 Generated with Claude Code

- Create CHANGELOG.md with history from git (v2.3.10 - v2.3.14)
- Add fastlane/changelog_helper.rb for changelog extraction and validation
- Modify release.yml to validate changelog exists before release
- Update release.yml to generate GitHub Release notes from CHANGELOG.md
- Update release.yml to generate Google Play whatsnew from CHANGELOG.md
- Add validate_changelog and version_info lanes to Fastfile
- Add changelog validation to deploy_beta and deploy_production lanes

This aligns with the iOS version's changelog workflow where:
- Developers manually maintain CHANGELOG.md with version entries
- Release will fail if no changelog entry exists for current version
- Release notes are automatically extracted from CHANGELOG.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Copilot AI review requested due to automatic review settings December 27, 2025 08:42
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a CHANGELOG.md-based release notes mechanism to replace the previous git commit-based approach. The implementation adds changelog validation to the release pipeline, ensuring that every release has a corresponding changelog entry before proceeding.

Key changes:

  • Created CHANGELOG.md with historical version entries (v2.3.10 - v2.3.14) in a structured format
  • Added Ruby helper module for changelog parsing and validation with locale-specific formatting
  • Modified release workflow to validate changelog presence and extract release notes from CHANGELOG.md

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 15 comments.

File Description
CHANGELOG.md New changelog file documenting version history with Feature/Fix/Improvement categorization
fastlane/changelog_helper.rb Ruby module for extracting and formatting changelog entries for GitHub and Google Play
fastlane/Fastfile Added validate_changelog and version_info lanes, integrated validation into deploy lanes
.github/workflows/release.yml Replaced git-based changelog generation with CHANGELOG.md extraction, added validation step

Comment on lines 180 to 184
feedback_header = "Feedback: https://v2er.app/help\n\n"
feedback_header_zh = "唯一问题反馈渠道:https://v2er.app/help\n\n"

# Reserve space for feedback header
available_space = GOOGLE_PLAY_LIMIT - feedback_header.length
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The feedback header includes a newline at the end (\n\n), but when calculating available_space on line 184, only the length of the string literal is used. The literal string "Feedback: https://v2er.app/help\n\n" counts \n as two characters (backslash + n), not as actual newlines. This means the actual length when used will differ from the calculated length, potentially causing the content to exceed the GOOGLE_PLAY_LIMIT. Consider using actual newlines in the string or using .length on the actual string that will be concatenated.

Copilot uses AI. Check for mistakes.
Comment on lines 583 to 604
current_len=${#content}
# Feedback header
FEEDBACK_HEADER_EN="Feedback: https://v2er.app/help"
FEEDBACK_HEADER_ZH="唯一问题反馈渠道:https://v2er.app/help"
# Build release notes for each version
declare -a version_notes_en
declare -a version_notes_zh
# Current version (HEAD to last tag or all commits if no tags)
declare -A curr_features curr_bugs curr_improvements curr_performance
if [ ${#TAGS[@]} -gt 0 ]; then
collect_version_commits "${TAGS[0]}..HEAD" curr_features curr_bugs curr_improvements curr_performance
else
collect_version_commits "HEAD~10..HEAD" curr_features curr_bugs curr_improvements curr_performance
fi
CURR_NOTES=$(generate_version_notes curr_features curr_bugs curr_improvements curr_performance)
if [ -n "$CURR_NOTES" ]; then
version_notes_en+=("$CURR_NOTES")
version_notes_zh+=("$CURR_NOTES")
fi
# Add changelog for each version
for i in "${!VERSIONS[@]}"; do
version="${VERSIONS[$i]}"
raw_changelog=$(extract_version_changelog "$version")
formatted=$(format_for_google_play "$raw_changelog")

# Previous versions (from tags)
for i in 0 1; do
if [ $i -lt ${#TAGS[@]} ] && [ $((i+1)) -lt ${#TAGS[@]} ]; then
declare -A prev_features prev_bugs prev_improvements prev_performance
collect_version_commits "${TAGS[$((i+1))]}..${TAGS[$i]}" prev_features prev_bugs prev_improvements prev_performance
PREV_NOTES=$(generate_version_notes prev_features prev_bugs prev_improvements prev_performance)
if [ -n "$PREV_NOTES" ]; then
version_notes_en+=("--- ${TAGS[$i]} ---"$'\n'"$PREV_NOTES")
version_notes_zh+=("--- ${TAGS[$i]} ---"$'\n'"$PREV_NOTES")
if [ -z "$formatted" ]; then
continue
fi
unset prev_features prev_bugs prev_improvements prev_performance
elif [ $i -lt ${#TAGS[@]} ] && [ $((i+1)) -ge ${#TAGS[@]} ]; then
# Last tag - get commits before it
declare -A prev_features prev_bugs prev_improvements prev_performance
collect_version_commits "${TAGS[$i]}~5..${TAGS[$i]}" prev_features prev_bugs prev_improvements prev_performance
PREV_NOTES=$(generate_version_notes prev_features prev_bugs prev_improvements prev_performance)
if [ -n "$PREV_NOTES" ]; then
version_notes_en+=("--- ${TAGS[$i]} ---"$'\n'"$PREV_NOTES")
version_notes_zh+=("--- ${TAGS[$i]} ---"$'\n'"$PREV_NOTES")

if [ "$i" -eq 0 ]; then
version_section="$formatted"
else
version_section="
--- v${version} ---
${formatted}"
fi
unset prev_features prev_bugs prev_improvements prev_performance
fi
done

# Generate English release notes with character limit
{
echo "$FEEDBACK_HEADER_EN"
echo ""
echo "V2er $CURRENT_VERSION"
echo ""
} > whatsnew/whatsnew-en-US
section_len=${#version_section}
if [ $((current_len + section_len)) -lt "$max_chars" ]; then
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The character length calculation uses ${#content} which counts characters, but in bash this counts bytes for multi-byte UTF-8 characters, not actual characters. Since Google Play's 500-character limit likely refers to Unicode characters, this could cause issues if the changelog contains non-ASCII characters (e.g., Chinese characters, emojis). The Ruby version has the same potential issue. Consider using a method that counts actual Unicode characters, or ensure that the character count matches Google Play's counting method.

Copilot uses AI. Check for mistakes.
echo ""
} > whatsnew/whatsnew-en-US
section_len=${#version_section}
if [ $((current_len + section_len)) -lt "$max_chars" ]; then
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The comparison operator -lt is used for a less-than check, but should be -le (less than or equal) to properly respect the character limit. If the current length plus section length exactly equals MAX_CHARS, the content should still be included since it doesn't exceed the limit. Using -lt means content that would fit exactly at the limit would be incorrectly excluded.

Suggested change
if [ $((current_len + section_len)) -lt "$max_chars" ]; then
if [ $((current_len + section_len)) -le "$max_chars" ]; then

Copilot uses AI. Check for mistakes.
CHANGELOG.md Outdated

Example format:
```
## vX.Y.Z (Build N)
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The documentation states to use "vX.Y.Z" format in the example (line 55), but the actual changelog entries use "v2.3.14" format with lowercase 'v' and the version header pattern in the code matches both formats. For consistency and to avoid confusion, the example should match the actual format used in the file. Update the example to use lowercase 'v' to match the actual entries above.

Copilot uses AI. Check for mistakes.

# Check if adding this section would exceed the limit
current_length = combined_parts.join.length
if current_length + version_section.length <= available_space
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

Similar issue as in release.yml line 604: the comparison uses -lt instead of -le. When current_length + version_section.length exactly equals available_space, the content should be included since it fits within the limit. Using -lt incorrectly excludes content that would fit exactly at the boundary.

Copilot uses AI. Check for mistakes.
formatted
end

# Get changelog for current version and up to 2 previous versions
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The function header comment says "Get changelog for current version and up to 2 previous versions" but the code on line 176 uses all_versions[current_index, 3] which gets up to 3 versions total (current + 2 previous). The comment should say "up to 3 versions (current + 2 previous)" for accuracy, or the implementation should be changed if only 2 versions total were intended.

Suggested change
# Get changelog for current version and up to 2 previous versions
# Get changelog for current version and up to 2 previous versions (3 versions total)

Copilot uses AI. Check for mistakes.
Comment on lines +578 to +583
content="${header}
# Get last 3 tags for multi-version release notes
TAGS=($(git tag --sort=-v:refname | head -3))
CURRENT_VERSION="${{ needs.prepare.outputs.version }}"
V2er ${CURRENT_VERSION}

echo "Found tags: ${TAGS[*]}"
echo "Current version: $CURRENT_VERSION"
"
current_len=${#content}
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The heredoc-style string definition starting with content="${header} and continuing over multiple lines (578-582) embeds literal newlines into the content variable. However, the way bash handles this with the closing quote on line 582 creates the string with actual newlines. This is correct, but the length calculation on line 583 using ${#content} will count the actual newlines as single bytes. Ensure this matches how Google Play counts characters in release notes.

Copilot uses AI. Check for mistakes.
fi
# Check if changelog has entry for this version
if grep -qE "^##\s+v?${VERSION_NUMBER}(\s|$)" CHANGELOG.md; then
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The grep pattern on line 158 uses (\s|$) to match either whitespace or end of line after the version number, but this doesn't account for the build number that appears in actual changelog entries like "## v2.3.14 (Build 244)". The pattern should handle the optional build number: grep -qE "^##\s+v?${VERSION_NUMBER}(\s+\(Build\s+[0-9]+\))?\s*$" to match the actual format used in CHANGELOG.md.

Suggested change
if grep -qE "^##\s+v?${VERSION_NUMBER}(\s|$)" CHANGELOG.md; then
if grep -qE "^##\s+v?${VERSION_NUMBER}(\s+\(Build\s+[0-9]+\))?\s*$" CHANGELOG.md; then

Copilot uses AI. Check for mistakes.
BEGIN { found=0; printing=0 }
/^##[[:space:]]+v?[0-9]+\.[0-9]+\.[0-9]+/ {
if (found && printing) { exit }
if ($0 ~ "v?" ver) { found=1; printing=1; next }
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

The awk pattern $0 ~ "v?" ver on line 397 performs a literal substring match, not a regex match. This means it will match "v?" (the literal characters 'v' and '?') concatenated with the version string, which is not the intended behavior. To make the 'v' optional as intended, the pattern needs to be a proper regex. Consider using: match($0, "^##[[:space:]]+v?" ver "([[:space:]]|\\(|$)") or building the regex pattern correctly.

Suggested change
if ($0 ~ "v?" ver) { found=1; printing=1; next }
if (match($0, "^##[[:space:]]+v?" ver "([[:space:]]|\\(|$)")) { found=1; printing=1; next }

Copilot uses AI. Check for mistakes.
BEGIN { found=0; printing=0 }
/^##[[:space:]]+v?[0-9]+\.[0-9]+\.[0-9]+/ {
if (found && printing) { exit }
if ($0 ~ "v?" ver) { found=1; printing=1; next }
Copy link

Copilot AI Dec 27, 2025

Choose a reason for hiding this comment

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

Same issue as line 397: the pattern $0 ~ "v?" ver performs a literal substring match for "v?" followed by the version, not a regex match with optional 'v'. This will fail to match version headers in CHANGELOG.md. Use proper regex construction like match($0, "^##[[:space:]]+v?" ver "([[:space:]]|\\(|$)") to make the 'v' optional as intended.

Suggested change
if ($0 ~ "v?" ver) { found=1; printing=1; next }
if (match($0, "^##[[:space:]]+v?" ver "([[:space:]]|\\(|$)")) { found=1; printing=1; next }

Copilot uses AI. Check for mistakes.
- Fix awk pattern to use proper regex match for version detection
- Use -le instead of -lt for character limit comparison (include at exact limit)
- Fix comment wording: "add entry" instead of "update"
- Update CHANGELOG.md example to use realistic version format
- Add clarifying comment about RELEASE_NOTES.md vs CHANGELOG.md
- Fix regex pattern in changelog validation to handle optional build number
- Remove unnecessary .strip() call in Ruby version extraction
- Update function comment to clarify "3 versions total"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@graycreate graycreate merged commit d9f2ae0 into main Dec 27, 2025
4 checks passed
@graycreate graycreate deleted the feature/changelog-mechanism branch December 27, 2025 08:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants