mirror of
https://github.com/chenasraf/pantry-flutter.git
synced 2026-05-17 17:28:03 +00:00
324 lines
9.5 KiB
Ruby
324 lines
9.5 KiB
Ruby
require "digest"
|
|
require "json"
|
|
|
|
# -- Shared helpers --
|
|
|
|
def version_info
|
|
pubspec = File.read(File.expand_path("../pubspec.yaml", __dir__))
|
|
version = pubspec.match(/^version:\s*(.+)$/)[1].strip
|
|
name, build = version.split("+")
|
|
{ name: name, build: build }
|
|
end
|
|
|
|
def version_name
|
|
v = version_info
|
|
"#{v[:build]} (#{v[:name]})"
|
|
end
|
|
|
|
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)
|
|
escaped = Regexp.escape(version)
|
|
pattern = /^## (?:\[#{escaped}\]|#{escaped}[\s(]).*?\n(.*?)(?=^## |\z)/m
|
|
match = changelog.match(pattern)
|
|
return "Release #{version}" unless match
|
|
|
|
match[1].strip
|
|
.gsub(/\s*\(\[[\da-f]+\]\([^)]+\)\)/, "")
|
|
.gsub(/^\*\s+\*\*[^*]+:\*\*\s*/m, "- ")
|
|
.gsub(/^\*\s+/, "- ")
|
|
.gsub(/^### /, "")
|
|
.gsub(/\n{3,}/, "\n\n")
|
|
.strip
|
|
.then { |n| n.empty? ? "Release #{version}" : n }
|
|
end
|
|
|
|
# -- Android --
|
|
|
|
default_platform(:android)
|
|
|
|
platform :android do
|
|
# 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 play_changelog
|
|
notes = changelog_notes
|
|
return notes if notes.length <= PLAY_NOTES_MAX
|
|
|
|
budget = PLAY_NOTES_MAX - PLAY_NOTES_TRAILER.length
|
|
truncated = notes[0, budget]
|
|
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 --
|
|
|
|
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"
|
|
lane :deploy do |options|
|
|
# Write changelog if not already present (CI generates it during the
|
|
# release PR, but this covers manual deploys).
|
|
version_code = version_info[:build]
|
|
changelog_dir = File.expand_path("metadata/android/en-US/changelogs", __dir__)
|
|
changelog_file = File.join(changelog_dir, "#{version_code}.txt")
|
|
unless File.exist?(changelog_file)
|
|
FileUtils.mkdir_p(changelog_dir)
|
|
File.write(changelog_file, play_changelog)
|
|
end
|
|
|
|
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,
|
|
skip_upload_changelogs: 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"
|
|
lane :promote do |options|
|
|
from = options[:from] || "internal"
|
|
to = options[:to] || "production"
|
|
status = options[:status] || "draft"
|
|
|
|
upload_to_play_store(
|
|
track: from,
|
|
track_promote_to: to,
|
|
release_status: status,
|
|
version_name: version_name,
|
|
skip_upload_aab: true,
|
|
skip_upload_apk: true,
|
|
skip_upload_metadata: true,
|
|
skip_upload_changelogs: true,
|
|
skip_upload_images: true,
|
|
skip_upload_screenshots: true,
|
|
)
|
|
end
|
|
end
|
|
|
|
# -- iOS --
|
|
|
|
platform :ios do
|
|
def api_key
|
|
key_id = ENV.fetch("APP_STORE_API_KEY")
|
|
app_store_connect_api_key(
|
|
key_id: key_id,
|
|
issuer_id: ENV.fetch("APP_STORE_ISSUER_ID"),
|
|
key_filepath: File.join(ENV.fetch("APP_STORE_KEY_PATH"), "AuthKey_#{key_id}.p8"),
|
|
)
|
|
end
|
|
|
|
def find_ipa
|
|
ipa_path = Dir[File.expand_path("../build/ios/ipa/*.ipa", __dir__)].first
|
|
UI.user_error!("No IPA found in build/ios/ipa/. Run 'make ios-build' first.") unless ipa_path
|
|
ipa_path
|
|
end
|
|
|
|
def sync_release_notes
|
|
version_code = version_info[:build]
|
|
changelog_file = File.expand_path("metadata/ios/en-US/changelogs/#{version_code}.txt", __dir__)
|
|
notes = File.exist?(changelog_file) ? File.read(changelog_file).strip : changelog_notes
|
|
release_notes_path = File.expand_path("metadata/ios/en-US/release_notes.txt", __dir__)
|
|
File.write(release_notes_path, notes)
|
|
UI.message("Synced release notes from build #{version_code} (#{notes.length} chars)")
|
|
notes
|
|
end
|
|
|
|
desc "Upload to TestFlight"
|
|
lane :beta do
|
|
notes = sync_release_notes
|
|
upload_to_testflight(
|
|
api_key: api_key,
|
|
ipa: find_ipa,
|
|
changelog: notes,
|
|
skip_waiting_for_build_processing: true,
|
|
)
|
|
end
|
|
|
|
desc "Upload to App Store"
|
|
lane :release do
|
|
sync_release_notes
|
|
deliver(
|
|
api_key: api_key,
|
|
ipa: find_ipa,
|
|
metadata_path: File.expand_path("metadata/ios", __dir__),
|
|
screenshots_path: File.expand_path("metadata/ios/en-US/screenshots", __dir__),
|
|
skip_screenshots: true,
|
|
submit_for_review: true,
|
|
precheck_include_in_app_purchases: false,
|
|
force: true,
|
|
)
|
|
end
|
|
|
|
desc "Sync iOS metadata only (no IPA upload)"
|
|
lane :metadata do
|
|
deliver(
|
|
api_key: api_key,
|
|
metadata_path: File.expand_path("metadata/ios", __dir__),
|
|
screenshots_path: File.expand_path("metadata/ios/en-US/screenshots", __dir__),
|
|
skip_binary_upload: true,
|
|
skip_screenshots: true,
|
|
submit_for_review: false,
|
|
precheck_include_in_app_purchases: false,
|
|
force: true,
|
|
)
|
|
end
|
|
|
|
desc "Submit existing App Store build for review (no IPA upload)"
|
|
lane :submit do
|
|
deliver(
|
|
api_key: api_key,
|
|
metadata_path: File.expand_path("metadata/ios", __dir__),
|
|
screenshots_path: File.expand_path("metadata/ios/en-US/screenshots", __dir__),
|
|
skip_binary_upload: true,
|
|
skip_screenshots: true,
|
|
submit_for_review: true,
|
|
precheck_include_in_app_purchases: false,
|
|
force: true,
|
|
)
|
|
end
|
|
end
|
|
|
|
# -- macOS --
|
|
|
|
platform :mac do
|
|
def api_key
|
|
key_id = ENV.fetch("APP_STORE_API_KEY")
|
|
app_store_connect_api_key(
|
|
key_id: key_id,
|
|
issuer_id: ENV.fetch("APP_STORE_ISSUER_ID"),
|
|
key_filepath: File.join(ENV.fetch("APP_STORE_KEY_PATH"), "AuthKey_#{key_id}.p8"),
|
|
)
|
|
end
|
|
|
|
def find_pkg
|
|
pkg_path = Dir[File.expand_path("../build/macos/pkg/*.pkg", __dir__)].first
|
|
UI.user_error!("No PKG found in build/macos/pkg/. Run 'make macos-build-pkg' first.") unless pkg_path
|
|
pkg_path
|
|
end
|
|
|
|
def sync_release_notes
|
|
version_code = version_info[:build]
|
|
changelog_file = File.expand_path("metadata/macos/en-US/changelogs/#{version_code}.txt", __dir__)
|
|
notes = File.exist?(changelog_file) ? File.read(changelog_file).strip : changelog_notes
|
|
release_notes_path = File.expand_path("metadata/macos/en-US/release_notes.txt", __dir__)
|
|
File.write(release_notes_path, notes)
|
|
UI.message("Synced release notes from build #{version_code} (#{notes.length} chars)")
|
|
notes
|
|
end
|
|
|
|
desc "Upload to TestFlight (macOS)"
|
|
lane :beta do
|
|
notes = sync_release_notes
|
|
upload_to_testflight(
|
|
api_key: api_key,
|
|
pkg: find_pkg,
|
|
app_platform: "osx",
|
|
changelog: notes,
|
|
skip_waiting_for_build_processing: true,
|
|
)
|
|
end
|
|
|
|
desc "Upload to Mac App Store"
|
|
lane :release do
|
|
sync_release_notes
|
|
deliver(
|
|
api_key: api_key,
|
|
pkg: find_pkg,
|
|
platform: "osx",
|
|
metadata_path: File.expand_path("metadata/macos", __dir__),
|
|
screenshots_path: File.expand_path("metadata/macos/en-US/screenshots", __dir__),
|
|
skip_screenshots: true,
|
|
submit_for_review: true,
|
|
precheck_include_in_app_purchases: false,
|
|
force: true,
|
|
)
|
|
end
|
|
|
|
desc "Sync macOS metadata only (no PKG upload)"
|
|
lane :metadata do
|
|
deliver(
|
|
api_key: api_key,
|
|
platform: "osx",
|
|
metadata_path: File.expand_path("metadata/macos", __dir__),
|
|
screenshots_path: File.expand_path("metadata/macos/en-US/screenshots", __dir__),
|
|
skip_binary_upload: true,
|
|
skip_screenshots: true,
|
|
submit_for_review: false,
|
|
precheck_include_in_app_purchases: false,
|
|
force: true,
|
|
)
|
|
end
|
|
|
|
desc "Submit existing Mac App Store build for review (no PKG upload)"
|
|
lane :submit do
|
|
deliver(
|
|
api_key: api_key,
|
|
platform: "osx",
|
|
metadata_path: File.expand_path("metadata/macos", __dir__),
|
|
screenshots_path: File.expand_path("metadata/macos/en-US/screenshots", __dir__),
|
|
skip_binary_upload: true,
|
|
skip_screenshots: true,
|
|
submit_for_review: true,
|
|
precheck_include_in_app_purchases: false,
|
|
force: true,
|
|
)
|
|
end
|
|
end
|