diff --git a/.gitignore b/.gitignore index fe72447..f7a4973 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ android/app/*.keystore # Fastlane android/fastlane/play-store-key.json +android/fastlane/.image_hashes.json ios/fastlane/report.xml android/fastlane/report.xml /.envrc diff --git a/android/fastlane/Fastfile b/android/fastlane/Fastfile index aaa2087..5c952ce 100644 --- a/android/fastlane/Fastfile +++ b/android/fastlane/Fastfile @@ -1,3 +1,6 @@ +require "digest" +require "json" + default_platform(:android) platform :android do @@ -13,17 +16,24 @@ platform :android do "#{v[:build]} (#{v[:name]})" end + # Google Play enforces a 500-char limit on release notes per language. + PLAY_NOTES_MAX = 500 + PLAY_NOTES_TRAILER = "\n\n… see full notes on GitHub." + def changelog_notes version = version_info[:name] changelog_path = File.expand_path("../../CHANGELOG.md", __dir__) return "Release #{version}" unless File.exist?(changelog_path) changelog = File.read(changelog_path) - pattern = /^## \[#{Regexp.escape(version)}\].*?\n(.*?)(?=^## \[|\z)/m + # Match the release heading (both `## [x.y.z]...` and `## x.y.z ...` forms), + # capture until the next `## ` heading (any form) or EOF. + escaped = Regexp.escape(version) + pattern = /^## (?:\[#{escaped}\]|#{escaped}[\s(]).*?\n(.*?)(?=^## |\z)/m match = changelog.match(pattern) return "Release #{version}" unless match - match[1].strip + notes = match[1].strip .gsub(/\s*\(\[[\da-f]+\]\([^)]+\)\)/, "") .gsub(/^\*\s+\*\*[^*]+:\*\*\s*/m, "- ") .gsub(/^\*\s+/, "- ") @@ -31,6 +41,56 @@ platform :android do .gsub(/\n{3,}/, "\n\n") .strip .then { |n| n.empty? ? "Release #{version}" : n } + + truncate_for_play(notes) + end + + # Truncate release notes to fit within the Play Store limit. If the + # notes exceed the limit, trim to a line boundary and append a trailer + # so the total stays under [PLAY_NOTES_MAX]. + def truncate_for_play(notes) + return notes if notes.length <= PLAY_NOTES_MAX + + budget = PLAY_NOTES_MAX - PLAY_NOTES_TRAILER.length + truncated = notes[0, budget] + + # Cut at the last newline so we don't chop a bullet mid-word. + last_newline = truncated.rindex("\n") + truncated = truncated[0, last_newline] if last_newline && last_newline > budget / 2 + + truncated.rstrip + PLAY_NOTES_TRAILER + end + + # -- Image change detection -- + # We hash every file under metadata/android/*/images/ and compare to a + # cache file. If nothing changed, we skip image uploads entirely. + + IMAGE_HASH_CACHE = File.expand_path(".image_hashes.json", __dir__) + + def current_image_hashes + root = File.expand_path("metadata/android", __dir__) + return {} unless Dir.exist?(root) + + files = Dir.glob(File.join(root, "**/images/**/*")).select { |f| File.file?(f) } + files.sort.each_with_object({}) do |path, acc| + rel = path.sub("#{root}/", "") + acc[rel] = Digest::SHA256.file(path).hexdigest + end + end + + def cached_image_hashes + return {} unless File.exist?(IMAGE_HASH_CACHE) + JSON.parse(File.read(IMAGE_HASH_CACHE)) + rescue StandardError + {} + end + + def images_changed? + current_image_hashes != cached_image_hashes + end + + def save_image_hashes + File.write(IMAGE_HASH_CACHE, JSON.pretty_generate(current_image_hashes)) end desc "Upload AAB to Google Play" @@ -42,22 +102,36 @@ platform :android do FileUtils.mkdir_p(changelog_dir) File.write(File.join(changelog_dir, "#{version_code}.txt"), changelog_notes) + changed = images_changed? + UI.message(changed ? "Images changed — uploading." : "Images unchanged — skipping.") + upload_to_play_store( aab: File.expand_path("../../build/app/outputs/bundle/release/app-release.aab", __dir__), track: options[:track] || "internal", release_status: options[:status] || "draft", version_name: version_name, metadata_path: File.expand_path("metadata/android", __dir__), + skip_upload_images: !changed, + skip_upload_screenshots: !changed, ) + + save_image_hashes if changed end desc "Sync metadata and screenshots only (no AAB upload)" lane :metadata do + changed = images_changed? + UI.message(changed ? "Images changed — uploading." : "Images unchanged — skipping.") + upload_to_play_store( skip_upload_aab: true, skip_upload_apk: true, metadata_path: File.expand_path("metadata/android", __dir__), + skip_upload_images: !changed, + skip_upload_screenshots: !changed, ) + + save_image_hashes if changed end desc "Promote a release from one track to another" diff --git a/android/fastlane/metadata/android/en-US/changelogs/3.txt b/android/fastlane/metadata/android/en-US/changelogs/3.txt new file mode 100644 index 0000000..727476e --- /dev/null +++ b/android/fastlane/metadata/android/en-US/changelogs/3.txt @@ -0,0 +1,8 @@ +Features + +- add sorting by category for checklist +- notifications support + +Bug Fixes + +- add bottom padding to accomodate fab