mirror of
https://github.com/priyanshujain/margin.git
synced 2026-10-02 19:17:03 +00:00
feat(appstore): script the provisioning, listing and TestFlight setup
The listing copy lives in text files rather than in a web form, so changing a description is a diff someone can read and the store listing is reviewable next to the code it describes. Field lengths are checked before sending, because Apple rejects an over-length field with an error that never names the limit. apple-provision.rb drives the Developer Portal through spaceship and is find-or-create throughout. That matters most for the Developer ID certificate: an account may hold only a handful, they cannot be un-revoked, and every copy of the app already signed by one stops verifying if it goes away. Both scripts pin the App Store Connect team. This Apple ID can see more than one, and the other belongs to somebody else entirely, so letting spaceship choose is how a listing ends up on the wrong account. The reviewer phone number and email are deliberately not in here. Apple requires a real number and this repo is public. Claude-Session: https://claude.ai/code/session_018HwEWvoE1NkkUjJCMLSnup
This commit is contained in:
1 parent
92b446973f
commit
6ddb3ac43e
23 files changed
+790
No files matched your search
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env ruby
|
||||
# Upload the rendered frames in appstore/screenshots to the App Store listing.
|
||||
#
|
||||
# [email protected] ruby scripts/appstore-screenshots.rb
|
||||
#
|
||||
# The frames are rendered by the HTML under appstore/screenshots/src, so this only ever moves
|
||||
# finished PNGs. Rerunning replaces what is there rather than appending, because a listing that
|
||||
# quietly accumulated ten frames across five runs would be worse than one that is simply current.
|
||||
begin
|
||||
require "spaceship"
|
||||
rescue LoadError
|
||||
libexec = Dir["/opt/homebrew/Cellar/fastlane/*/libexec", "/usr/local/Cellar/fastlane/*/libexec"].max
|
||||
abort "spaceship is not installed. `brew install fastlane` and rerun." unless libexec
|
||||
ENV["GEM_PATH"] = [libexec, ENV["GEM_PATH"]].compact.join(":")
|
||||
Gem.clear_paths
|
||||
require "spaceship"
|
||||
end
|
||||
|
||||
require "shellwords"
|
||||
|
||||
DIR = ENV.fetch("SCREENSHOT_DIR", "appstore/screenshots")
|
||||
LOCALE = "en-US"
|
||||
BUNDLE_ID = ENV.fetch("BUNDLE_ID", "studio.margin.app")
|
||||
ENV["FASTLANE_ITC_TEAM_ID"] = ENV.fetch("FASTLANE_ITC_TEAM_ID", "129377371")
|
||||
|
||||
# The only size App Store Connect takes for a Mac app, out of the four it documents, that this
|
||||
# pipeline renders. Anything else is a mistake worth stopping for.
|
||||
EXPECTED = [2560, 1600].freeze
|
||||
|
||||
frames = Dir[File.join(DIR, "frame-*.png")].sort
|
||||
abort "No frames in #{DIR}." if frames.empty?
|
||||
|
||||
frames.each do |path|
|
||||
dimensions = `sips -g pixelWidth -g pixelHeight #{path.shellescape} 2>/dev/null`
|
||||
.scan(/pixel(?:Width|Height):\s*(\d+)/).flatten.map(&:to_i)
|
||||
next if dimensions == EXPECTED
|
||||
|
||||
abort "#{path} is #{dimensions.join('x')}, and Apple wants #{EXPECTED.join('x')}."
|
||||
end
|
||||
puts "#{frames.size} frames, all #{EXPECTED.join('x')}"
|
||||
|
||||
Spaceship::ConnectAPI.login(ENV["APPLE_EMAIL"], nil, use_portal: false, use_tunes: true)
|
||||
|
||||
app = Spaceship::ConnectAPI::App.find(BUNDLE_ID)
|
||||
abort "No app for #{BUNDLE_ID}." unless app
|
||||
puts "#{app.name} (#{app.id})"
|
||||
|
||||
version = app.get_edit_app_store_version(platform: Spaceship::ConnectAPI::Platform::MAC_OS)
|
||||
abort "No editable macOS version." unless version
|
||||
|
||||
localization = version.get_app_store_version_localizations.find { |l| l.locale == LOCALE }
|
||||
abort "No #{LOCALE} localization; run appstore-listing.rb first." unless localization
|
||||
|
||||
display_type = Spaceship::ConnectAPI::AppScreenshotSet::DisplayType::APP_DESKTOP
|
||||
set = localization.get_app_screenshot_sets.find { |s| s.screenshot_display_type == display_type }
|
||||
|
||||
if set
|
||||
set.app_screenshots.each(&:delete!)
|
||||
puts " cleared #{set.app_screenshots.size} existing screenshots"
|
||||
else
|
||||
set = localization.create_app_screenshot_set(attributes: { screenshotDisplayType: display_type })
|
||||
puts " created the desktop screenshot set"
|
||||
end
|
||||
|
||||
frames.each_with_index do |path, index|
|
||||
set.upload_screenshot(path: path, position: index)
|
||||
puts " uploaded #{File.basename(path)}"
|
||||
end
|
||||
|
||||
puts
|
||||
puts "Apple processes each image before it counts as attached; give it a minute before checking."
|
||||
Reference in new issue
Block a user