diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..dc6085f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,38 @@ +# Changelog + +All notable changes to V2er iOS app will be documented in this file. + +## v1.1.1 (Build 31) +1. Feature: Add feed filter menu with Reddit-style dropdown for better content filtering +2. Fix: Prevent crash when clicking Ignore/Report buttons without being logged in +3. Fix: Improve TestFlight beta distribution configuration +4. Feature: Enable automatic TestFlight beta distribution to public testers + +## v1.1.0 (Build 30) +1. Feature: Initial public beta release +2. Fix: Resolve iOS build hanging at code signing step +3. Fix: Improve version management system using xcconfig +4. Feature: Centralized version configuration in Version.xcconfig + +--- + +## How to Update Changelog + +When updating the version in `V2er/Config/Version.xcconfig`: + +1. Add a new version section at the top of this file +2. List all changes since the last version: + - Use "Feature:" for new features + - Use "Fix:" for bug fixes + - Use "Improvement:" for enhancements + - Use "Breaking:" for breaking changes + +Example format: +``` +## vX.Y.Z (Build N) +1. Feature: Description of new feature +2. Fix: Description of bug fix +3. Improvement: Description of enhancement +``` + +The changelog will be automatically extracted and used in TestFlight release notes. diff --git a/CLAUDE.md b/CLAUDE.md index 24cef8b..5266b6e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -99,6 +99,57 @@ Tests are located in: Currently contains only boilerplate test setup. +## Release Management + +### Version and Changelog Workflow + +Version information is centralized in `V2er/Config/Version.xcconfig`: +- `MARKETING_VERSION`: User-facing version (e.g., 1.1.1) +- `CURRENT_PROJECT_VERSION`: Build number (auto-incremented by CI) + +**When updating the version for a new release:** + +1. **Update Version.xcconfig** + ```bash + # Edit V2er/Config/Version.xcconfig + MARKETING_VERSION = 1.2.0 + ``` + +2. **Update CHANGELOG.md** + Add a new section at the top with your changes: + ```markdown + ## v1.2.0 (Build XX) + 1. Feature: Description of new feature + 2. Fix: Description of bug fix + 3. Improvement: Description of enhancement + ``` + +3. **Commit and push to trigger release** + ```bash + git add V2er/Config/Version.xcconfig CHANGELOG.md + git commit -m "chore: bump version to 1.2.0" + git push origin main + ``` + +The CI pipeline will: +- Validate that CHANGELOG.md contains an entry for the new version +- Extract the changelog for TestFlight release notes +- Auto-increment build number +- Upload to TestFlight with your changelog + +### Fastlane Commands + +```bash +# Build and upload to TestFlight (requires changelog) +fastlane beta + +# Distribute existing build to beta testers +fastlane distribute_beta + +# Sync certificates and provisioning profiles +fastlane sync_certificates +``` + ## Important Notes - Minimum iOS version: iOS 15.0 @@ -106,4 +157,5 @@ Currently contains only boilerplate test setup. - Orientation: Portrait only on iPhone, all orientations on iPad - UI Style: Light mode enforced - Website submodule: Located at `website/` (separate repository) -- create PR should always use english \ No newline at end of file +- Create PR should always use English +- **CHANGELOG.md is required** for all releases - the build will fail if the current version is missing from the changelog \ No newline at end of file diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a00fe14..daf379c 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,5 +1,8 @@ # Fastfile for V2er iOS app +# Import changelog helper +require_relative 'changelog_helper' + default_platform(:ios) platform :ios do @@ -105,6 +108,11 @@ platform :ios do desc "Build and upload to TestFlight" lane :beta do + # Validate that changelog exists for current version + unless ChangelogHelper.validate_changelog_exists + UI.user_error!("Please update CHANGELOG.md with an entry for the current version before releasing!") + end + # Ensure we have the latest certificates sync_certificates @@ -116,6 +124,7 @@ platform :ios do xcconfig_path = "../V2er/Config/Version.xcconfig" xcconfig_content = File.read(xcconfig_path) current_build_number = xcconfig_content.match(/CURRENT_PROJECT_VERSION = (\d+)/)[1].to_i + current_version = xcconfig_content.match(/MARKETING_VERSION = (.+)/)[1].strip latest_testflight = latest_testflight_build_number(api_key: api_key) @@ -154,6 +163,9 @@ platform :ios do # Build the app build_ipa + # Extract changelog for the current version + changelog_content = ChangelogHelper.extract_changelog(current_version) + # Upload to TestFlight upload_to_testflight( api_key: api_key, @@ -164,7 +176,7 @@ platform :ios do distribute_external: true, # Distribute to external testers (public beta) distribute_only: false, # Upload and distribute in one action groups: ["Public Beta", "External Testers", "Beta Testers"], # Public beta groups - changelog: "Bug fixes and improvements", + changelog: changelog_content, # Use changelog from CHANGELOG.md notify_external_testers: true, # Send email notifications to external testers uses_non_exempt_encryption: false, # Required for automatic distribution submit_beta_review: true, # Automatically submit for Beta review diff --git a/fastlane/changelog_helper.rb b/fastlane/changelog_helper.rb new file mode 100644 index 0000000..2302ee9 --- /dev/null +++ b/fastlane/changelog_helper.rb @@ -0,0 +1,149 @@ +# changelog_helper.rb +# Helper module to extract changelog entries from CHANGELOG.md + +module ChangelogHelper + # Extract changelog for a specific version from CHANGELOG.md + # @param version [String] The version to extract (e.g., "1.1.1") + # @return [String] The changelog content for the specified version + def self.extract_changelog(version) + changelog_path = File.expand_path("../CHANGELOG.md", __dir__) + + unless File.exist?(changelog_path) + UI.error("CHANGELOG.md not found at #{changelog_path}") + return "Bug fixes and improvements" + end + + content = File.read(changelog_path) + + # Match the version section, handling both "vX.Y.Z" and "X.Y.Z" formats + # Also capture optional build number like "v1.1.1 (Build 31)" + version_pattern = /^##\s+v?#{Regexp.escape(version)}(?:\s+\(Build\s+\d+\))?\s*$/ + + lines = content.lines + start_index = nil + end_index = nil + + # Find the start of the version section + lines.each_with_index do |line, index| + if line.match?(version_pattern) + start_index = index + break + end + end + + if start_index.nil? + UI.warning("Version #{version} not found in CHANGELOG.md") + UI.message("Available versions:") + lines.each do |line| + if line.match?(/^##\s+v?\d+\.\d+\.\d+/) + UI.message(" - #{line.strip}") + end + end + return "Bug fixes and improvements" + end + + # Find the end of the version section (next ## heading or ---) + ((start_index + 1)...lines.length).each do |index| + line = lines[index] + if line.match?(/^##\s+/) || line.match?(/^---/) + end_index = index + break + end + end + + end_index ||= lines.length + + # Extract the changelog content (skip the version header) + changelog_lines = lines[(start_index + 1)...end_index] + + # Remove leading/trailing empty lines and convert to string + changelog = changelog_lines + .join("") + .strip + + if changelog.empty? + UI.warning("No changelog content found for version #{version}") + return "Bug fixes and improvements" + end + + # Format for TestFlight (convert numbered list to bullet points if needed) + # TestFlight supports basic formatting + formatted_changelog = format_for_testflight(changelog) + + UI.success("Extracted changelog for version #{version}:") + UI.message(formatted_changelog) + + formatted_changelog + end + + # Format changelog content for TestFlight display + # @param content [String] Raw changelog content + # @return [String] Formatted changelog + def self.format_for_testflight(content) + # TestFlight supports: + # - Plain text + # - Line breaks + # - Basic formatting + + # Convert numbered lists to bullet points for better readability + # "1. Feature: xxx" -> "• Feature: xxx" + formatted = content.gsub(/^\d+\.\s+/, "• ") + + # Ensure we don't exceed TestFlight's changelog length limit (4000 chars) + if formatted.length > 3900 + formatted = formatted[0...3900] + "\n\n(See full changelog at github.com/v2er-app/iOS)" + end + + formatted + end + + # Get the current version from Version.xcconfig + # @return [String] The current marketing version + def self.get_current_version + xcconfig_path = File.expand_path("../V2er/Config/Version.xcconfig", __dir__) + + unless File.exist?(xcconfig_path) + UI.user_error!("Version.xcconfig not found at #{xcconfig_path}") + end + + content = File.read(xcconfig_path) + version_match = content.match(/MARKETING_VERSION\s*=\s*(.+)/) + + if version_match + version = version_match[1].strip + UI.message("Current version from Version.xcconfig: #{version}") + version + else + UI.user_error!("Could not find MARKETING_VERSION in Version.xcconfig") + end + end + + # Validate that changelog exists for the current version + # @return [Boolean] True if changelog exists, false otherwise + def self.validate_changelog_exists + current_version = get_current_version + changelog_path = File.expand_path("../CHANGELOG.md", __dir__) + + unless File.exist?(changelog_path) + UI.error("❌ CHANGELOG.md not found!") + UI.message("Please create CHANGELOG.md with an entry for version #{current_version}") + return false + end + + content = File.read(changelog_path) + version_pattern = /^##\s+v?#{Regexp.escape(current_version)}/ + + if content.match?(version_pattern) + UI.success("✅ Changelog entry found for version #{current_version}") + return true + else + UI.error("❌ No changelog entry found for version #{current_version}") + UI.message("Please add a changelog entry in CHANGELOG.md:") + UI.message("") + UI.message("## v#{current_version}") + UI.message("1. Feature/Fix: Description of changes") + UI.message("") + return false + end + end +end