mirror of
https://github.com/priyanshujain/margin.git
synced 2026-10-02 11:07:04 +00:00
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
96 lines
3.6 KiB
Ruby
96 lines
3.6 KiB
Ruby
#!/usr/bin/env ruby
|
|
# Declare the age rating and set pricing. These are the two things App Store Connect will not let a
|
|
# submission through without, and neither has anything to do with the copy in appstore-listing.rb.
|
|
#
|
|
# [email protected] ruby scripts/appstore-compliance.rb
|
|
#
|
|
# Every content answer here is NONE because Margin is an editor for words the person using it wrote
|
|
# themselves. It ships no media, has no feed, no other users, and no in-app browser. If any of that
|
|
# ever stops being true, this file is the thing that has to change with it.
|
|
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
|
|
|
|
BUNDLE_ID = ENV.fetch("BUNDLE_ID", "studio.margin.app")
|
|
ENV["FASTLANE_ITC_TEAM_ID"] = ENV.fetch("FASTLANE_ITC_TEAM_ID", "129377371")
|
|
|
|
NONE = Spaceship::ConnectAPI::AgeRatingDeclaration::Rating::NONE
|
|
|
|
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})"
|
|
|
|
info = app.fetch_edit_app_info
|
|
abort "No editable app info." unless info
|
|
|
|
declaration = info.fetch_age_rating_declaration
|
|
abort "No age rating declaration to write to." unless declaration
|
|
|
|
graded = {
|
|
alcoholTobaccoOrDrugUseOrReferences: NONE,
|
|
contests: NONE,
|
|
gamblingSimulated: NONE,
|
|
gunsOrOtherWeapons: NONE,
|
|
horrorOrFearThemes: NONE,
|
|
matureOrSuggestiveThemes: NONE,
|
|
medicalOrTreatmentInformation: NONE,
|
|
profanityOrCrudeHumor: NONE,
|
|
sexualContentGraphicAndNudity: NONE,
|
|
sexualContentOrNudity: NONE,
|
|
violenceCartoonOrFantasy: NONE,
|
|
violenceRealistic: NONE,
|
|
violenceRealisticProlongedGraphicOrSadistic: NONE,
|
|
}
|
|
|
|
# The app opens links in the system browser rather than rendering the web itself, and the only
|
|
# content it ever shows is the person's own writing, so there is no unrestricted web access and no
|
|
# user generated content in the sense Apple means: content from other people.
|
|
boolean = {
|
|
advertising: false,
|
|
ageAssurance: false,
|
|
gambling: false,
|
|
healthOrWellnessTopics: false,
|
|
lootBox: false,
|
|
messagingAndChat: false,
|
|
parentalControls: false,
|
|
unrestrictedWebAccess: false,
|
|
userGeneratedContent: false,
|
|
}
|
|
|
|
declaration.update(attributes: graded.merge(boolean))
|
|
puts " age rating declared, every content question answered NONE"
|
|
|
|
# App Privacy. Margin has no telemetry, no accounts and no server of its own, so nothing is
|
|
# collected. The Google Drive backup is the one thing that sends bytes anywhere, and it sends them
|
|
# to the account of the person who turned it on, which is not the developer collecting anything.
|
|
usages = Spaceship::ConnectAPI::AppDataUsage.all(
|
|
app_id: app.id, includes: "category,grouping,purpose,dataProtection"
|
|
)
|
|
|
|
if usages.any?(&:is_not_collected?)
|
|
puts " privacy already declared as data not collected"
|
|
else
|
|
Spaceship::ConnectAPI::AppDataUsage.create(app_id: app.id, app_data_usage_protection_id: "DATA_NOT_COLLECTED")
|
|
puts " privacy declared: data not collected"
|
|
end
|
|
|
|
state = Spaceship::ConnectAPI::AppDataUsagesPublishState.get(app_id: app.id)
|
|
if state.published
|
|
puts " privacy declaration already published"
|
|
else
|
|
state.publish!
|
|
puts " privacy declaration published"
|
|
end
|
|
|
|
# Free, everywhere. Territory availability is left alone: the default is all of them.
|
|
app.update(attributes: { pricing: [] }) if ENV["SET_PRICING"]
|
|
puts " pricing left as configured (the app is free, which is the default for a new record)"
|