mirror of
https://github.com/yattee/yattee.git
synced 2026-05-12 18:35:05 +00:00
New Release-DeveloperID configuration gates Sparkle behind a SPARKLE compile flag so the App Store Release build stays Sparkle-free. Adds SPUStandardUpdaterController wrapper, Check for Updates menu command, Advanced Settings section with beta channel toggle, and a Ruby script plus GitHub Actions job that signs each release and publishes the appcast to gh-pages for consumption by Sparkle and Homebrew cask.
368 lines
11 KiB
Ruby
368 lines
11 KiB
Ruby
# Yattee v2 Fastlane configuration
|
|
# Single unified "Yattee" scheme, platform selected via destination parameter
|
|
|
|
APP_NAME = ENV['APP_NAME'] || 'Yattee'
|
|
DEVELOPER_KEY_ID = ENV['DEVELOPER_KEY_ID']
|
|
DEVELOPER_KEY_ISSUER_ID = ENV['DEVELOPER_KEY_ISSUER_ID']
|
|
DEVELOPER_KEY_CONTENT = ENV['DEVELOPER_KEY_CONTENT']
|
|
TEAM_ID = ENV['TEAM_ID']
|
|
TEMP_KEYCHAIN_USER = ENV['TEMP_KEYCHAIN_USER']
|
|
TEMP_KEYCHAIN_PASSWORD = ENV['TEMP_KEYCHAIN_PASSWORD']
|
|
DEVELOPER_APP_IDENTIFIER = ENV['DEVELOPER_APP_IDENTIFIER']
|
|
GIT_AUTHORIZATION = ENV['GIT_AUTHORIZATION']
|
|
TESTFLIGHT_EXTERNAL_GROUPS = ENV['TESTFLIGHT_EXTERNAL_GROUPS']
|
|
|
|
XCODEPROJ = "#{APP_NAME}.xcodeproj"
|
|
SCHEME = APP_NAME
|
|
|
|
def delete_temp_keychain(name)
|
|
delete_keychain(
|
|
name: name
|
|
) if File.exist? File.expand_path("~/Library/Keychains/#{name}-db")
|
|
end
|
|
|
|
def create_temp_keychain(name, password)
|
|
create_keychain(
|
|
name: name,
|
|
password: password,
|
|
unlock: false,
|
|
timeout: 0
|
|
)
|
|
end
|
|
|
|
def ensure_temp_keychain(name, password)
|
|
delete_temp_keychain(name)
|
|
create_temp_keychain(name, password)
|
|
end
|
|
|
|
add_extra_platforms(platforms: [:tvos])
|
|
|
|
before_all do
|
|
# Skipping update_fastlane in CI for stability
|
|
end
|
|
|
|
desc "Get latest TestFlight build number across all platforms"
|
|
lane :latest_build_number do
|
|
api_key = app_store_connect_api_key(
|
|
key_id: DEVELOPER_KEY_ID,
|
|
issuer_id: DEVELOPER_KEY_ISSUER_ID,
|
|
key_content: DEVELOPER_KEY_CONTENT,
|
|
is_key_content_base64: true
|
|
)
|
|
|
|
ios_build = latest_testflight_build_number(api_key: api_key, app_identifier: DEVELOPER_APP_IDENTIFIER, platform: "ios")
|
|
tvos_build = latest_testflight_build_number(api_key: api_key, app_identifier: DEVELOPER_APP_IDENTIFIER, platform: "appletvos")
|
|
macos_build = latest_testflight_build_number(api_key: api_key, app_identifier: DEVELOPER_APP_IDENTIFIER, platform: "osx")
|
|
|
|
max_build = [ios_build, tvos_build, macos_build].max
|
|
# Write to repo root (Fastfile is in fastlane/ subdir)
|
|
output_path = File.expand_path("../latest_build_number.txt", __dir__)
|
|
File.write(output_path, max_build.to_s)
|
|
UI.success("Latest TestFlight build number: #{max_build}")
|
|
max_build
|
|
end
|
|
|
|
desc "Bump build number and commit"
|
|
lane :bump_build do
|
|
increment_build_number(xcodeproj: XCODEPROJ)
|
|
|
|
commit_version_bump(
|
|
message: "Bump build number to #{get_build_number(xcodeproj: XCODEPROJ)}",
|
|
xcodeproj: XCODEPROJ
|
|
)
|
|
end
|
|
|
|
desc "Bump version number and commit"
|
|
lane :bump_version do
|
|
increment_version_number(xcodeproj: XCODEPROJ)
|
|
|
|
commit_version_bump(
|
|
message: "Bump version number to #{get_version_number(xcodeproj: XCODEPROJ, target: SCHEME)}",
|
|
xcodeproj: XCODEPROJ
|
|
)
|
|
end
|
|
|
|
platform :ios do
|
|
desc "Push a new beta build to TestFlight"
|
|
lane :beta do
|
|
ensure_temp_keychain(TEMP_KEYCHAIN_USER, TEMP_KEYCHAIN_PASSWORD)
|
|
|
|
api_key = app_store_connect_api_key(
|
|
key_id: DEVELOPER_KEY_ID,
|
|
issuer_id: DEVELOPER_KEY_ISSUER_ID,
|
|
key_content: DEVELOPER_KEY_CONTENT,
|
|
is_key_content_base64: true
|
|
)
|
|
|
|
build = get_build_number(xcodeproj: XCODEPROJ)
|
|
version = get_version_number(
|
|
xcodeproj: XCODEPROJ,
|
|
target: SCHEME
|
|
)
|
|
|
|
match(
|
|
type: 'appstore',
|
|
platform: 'ios',
|
|
app_identifier: ["#{DEVELOPER_APP_IDENTIFIER}", "#{DEVELOPER_APP_IDENTIFIER}.ShareExtension"],
|
|
git_basic_authorization: Base64.strict_encode64(GIT_AUTHORIZATION),
|
|
readonly: true,
|
|
keychain_name: TEMP_KEYCHAIN_USER,
|
|
keychain_password: TEMP_KEYCHAIN_PASSWORD,
|
|
api_key: api_key
|
|
)
|
|
|
|
update_code_signing_settings(
|
|
use_automatic_signing: false,
|
|
path: XCODEPROJ,
|
|
team_id: TEAM_ID,
|
|
code_sign_identity: "Apple Distribution",
|
|
profile_name: "match AppStore #{DEVELOPER_APP_IDENTIFIER}",
|
|
targets: [SCHEME]
|
|
)
|
|
|
|
update_code_signing_settings(
|
|
use_automatic_signing: false,
|
|
path: XCODEPROJ,
|
|
team_id: TEAM_ID,
|
|
code_sign_identity: "Apple Distribution",
|
|
profile_name: "match AppStore #{DEVELOPER_APP_IDENTIFIER}.ShareExtension",
|
|
targets: ["YatteeShareExtension"]
|
|
)
|
|
|
|
build_app(
|
|
scheme: SCHEME,
|
|
destination: "generic/platform=iOS",
|
|
output_directory: "fastlane/builds/#{version}-#{build}/iOS",
|
|
output_name: "#{APP_NAME}-#{version}-iOS.ipa",
|
|
export_options: {
|
|
provisioningProfiles: {
|
|
"#{DEVELOPER_APP_IDENTIFIER}" => "match AppStore #{DEVELOPER_APP_IDENTIFIER}",
|
|
"#{DEVELOPER_APP_IDENTIFIER}.ShareExtension" => "match AppStore #{DEVELOPER_APP_IDENTIFIER}.ShareExtension"
|
|
}
|
|
}
|
|
)
|
|
|
|
changelog_path = File.expand_path('../CHANGELOG.md', __dir__)
|
|
changelog = File.exist?(changelog_path) ? File.read(changelog_path) : ""
|
|
|
|
upload_to_testflight(
|
|
api_key: api_key,
|
|
ipa: lane_context[SharedValues::IPA_OUTPUT_PATH],
|
|
changelog: changelog
|
|
)
|
|
end
|
|
end
|
|
|
|
platform :tvos do
|
|
desc "Push a new beta build to TestFlight"
|
|
lane :beta do
|
|
ensure_temp_keychain(TEMP_KEYCHAIN_USER, TEMP_KEYCHAIN_PASSWORD)
|
|
|
|
api_key = app_store_connect_api_key(
|
|
key_id: DEVELOPER_KEY_ID,
|
|
issuer_id: DEVELOPER_KEY_ISSUER_ID,
|
|
key_content: DEVELOPER_KEY_CONTENT,
|
|
is_key_content_base64: true
|
|
)
|
|
|
|
build = get_build_number(xcodeproj: XCODEPROJ)
|
|
version = get_version_number(
|
|
xcodeproj: XCODEPROJ,
|
|
target: SCHEME
|
|
)
|
|
|
|
match(
|
|
type: 'appstore',
|
|
platform: 'tvos',
|
|
app_identifier: ["#{DEVELOPER_APP_IDENTIFIER}", "#{DEVELOPER_APP_IDENTIFIER}.TopShelf"],
|
|
git_basic_authorization: Base64.strict_encode64(GIT_AUTHORIZATION),
|
|
readonly: true,
|
|
keychain_name: TEMP_KEYCHAIN_USER,
|
|
keychain_password: TEMP_KEYCHAIN_PASSWORD,
|
|
api_key: api_key
|
|
)
|
|
|
|
update_code_signing_settings(
|
|
use_automatic_signing: false,
|
|
path: XCODEPROJ,
|
|
team_id: TEAM_ID,
|
|
code_sign_identity: "Apple Distribution",
|
|
profile_name: "match AppStore #{DEVELOPER_APP_IDENTIFIER} tvos",
|
|
targets: [SCHEME]
|
|
)
|
|
|
|
update_code_signing_settings(
|
|
use_automatic_signing: false,
|
|
path: XCODEPROJ,
|
|
team_id: TEAM_ID,
|
|
code_sign_identity: "Apple Distribution",
|
|
profile_name: "match AppStore #{DEVELOPER_APP_IDENTIFIER}.TopShelf tvos",
|
|
targets: ["YatteeTopShelf"]
|
|
)
|
|
|
|
build_app(
|
|
scheme: SCHEME,
|
|
destination: "generic/platform=tvOS",
|
|
output_directory: "fastlane/builds/#{version}-#{build}/tvOS",
|
|
output_name: "#{APP_NAME}-#{version}-tvOS.ipa",
|
|
export_method: "app-store",
|
|
export_options: {
|
|
provisioningProfiles: {
|
|
"#{DEVELOPER_APP_IDENTIFIER}" => "match AppStore #{DEVELOPER_APP_IDENTIFIER} tvos",
|
|
"#{DEVELOPER_APP_IDENTIFIER}.TopShelf" => "match AppStore #{DEVELOPER_APP_IDENTIFIER}.TopShelf tvos"
|
|
}
|
|
}
|
|
)
|
|
|
|
changelog_path = File.expand_path('../CHANGELOG.md', __dir__)
|
|
changelog = File.exist?(changelog_path) ? File.read(changelog_path) : ""
|
|
|
|
upload_to_testflight(
|
|
api_key: api_key,
|
|
ipa: lane_context[SharedValues::IPA_OUTPUT_PATH],
|
|
changelog: changelog
|
|
)
|
|
end
|
|
end
|
|
|
|
platform :mac do
|
|
desc "Push a new beta build to TestFlight"
|
|
lane :beta do
|
|
ensure_temp_keychain(TEMP_KEYCHAIN_USER, TEMP_KEYCHAIN_PASSWORD)
|
|
|
|
api_key = app_store_connect_api_key(
|
|
key_id: DEVELOPER_KEY_ID,
|
|
issuer_id: DEVELOPER_KEY_ISSUER_ID,
|
|
key_content: DEVELOPER_KEY_CONTENT,
|
|
is_key_content_base64: true
|
|
)
|
|
|
|
build = get_build_number(xcodeproj: XCODEPROJ)
|
|
version = get_version_number(
|
|
xcodeproj: XCODEPROJ,
|
|
target: SCHEME
|
|
)
|
|
|
|
match(
|
|
type: 'appstore',
|
|
platform: 'macos',
|
|
additional_cert_types: ['mac_installer_distribution'],
|
|
app_identifier: "#{DEVELOPER_APP_IDENTIFIER}",
|
|
git_basic_authorization: Base64.strict_encode64(GIT_AUTHORIZATION),
|
|
readonly: true,
|
|
keychain_name: TEMP_KEYCHAIN_USER,
|
|
keychain_password: TEMP_KEYCHAIN_PASSWORD,
|
|
api_key: api_key
|
|
)
|
|
|
|
update_code_signing_settings(
|
|
use_automatic_signing: false,
|
|
path: XCODEPROJ,
|
|
team_id: TEAM_ID,
|
|
code_sign_identity: "Apple Distribution",
|
|
profile_name: "match AppStore #{DEVELOPER_APP_IDENTIFIER} macos",
|
|
targets: [SCHEME]
|
|
)
|
|
|
|
build_mac_app(
|
|
scheme: SCHEME,
|
|
sdk: "macosx",
|
|
destination: "generic/platform=macOS",
|
|
output_directory: "fastlane/builds/#{version}-#{build}/macOS",
|
|
output_name: "#{APP_NAME}-#{version}-macOS",
|
|
export_method: "app-store",
|
|
export_options: {
|
|
provisioningProfiles: {
|
|
"#{DEVELOPER_APP_IDENTIFIER}" => "match AppStore #{DEVELOPER_APP_IDENTIFIER} macos"
|
|
}
|
|
}
|
|
)
|
|
|
|
changelog_path = File.expand_path('../CHANGELOG.md', __dir__)
|
|
changelog = File.exist?(changelog_path) ? File.read(changelog_path) : ""
|
|
|
|
upload_to_testflight(
|
|
api_key: api_key,
|
|
pkg: lane_context[SharedValues::PKG_OUTPUT_PATH],
|
|
changelog: changelog
|
|
)
|
|
end
|
|
|
|
desc "Build for Developer ID distribution and notarize"
|
|
lane :build_and_notarize do
|
|
ensure_temp_keychain(TEMP_KEYCHAIN_USER, TEMP_KEYCHAIN_PASSWORD)
|
|
|
|
api_key = app_store_connect_api_key(
|
|
key_id: DEVELOPER_KEY_ID,
|
|
issuer_id: DEVELOPER_KEY_ISSUER_ID,
|
|
key_content: DEVELOPER_KEY_CONTENT,
|
|
is_key_content_base64: true
|
|
)
|
|
|
|
build = get_build_number(xcodeproj: XCODEPROJ)
|
|
version = get_version_number(
|
|
xcodeproj: XCODEPROJ,
|
|
target: SCHEME
|
|
)
|
|
|
|
match(
|
|
type: 'developer_id',
|
|
platform: 'macos',
|
|
app_identifier: "#{DEVELOPER_APP_IDENTIFIER}",
|
|
git_basic_authorization: Base64.strict_encode64(GIT_AUTHORIZATION),
|
|
readonly: true,
|
|
keychain_name: TEMP_KEYCHAIN_USER,
|
|
keychain_password: TEMP_KEYCHAIN_PASSWORD,
|
|
api_key: api_key
|
|
)
|
|
|
|
update_code_signing_settings(
|
|
use_automatic_signing: false,
|
|
path: XCODEPROJ,
|
|
team_id: TEAM_ID,
|
|
code_sign_identity: "Developer ID Application",
|
|
profile_name: "match Direct #{DEVELOPER_APP_IDENTIFIER} macos",
|
|
targets: [SCHEME],
|
|
build_configurations: ["Release-DeveloperID"]
|
|
)
|
|
|
|
build_mac_app(
|
|
scheme: SCHEME,
|
|
configuration: "Release-DeveloperID",
|
|
output_directory: "fastlane/builds/#{version}-#{build}/macOS",
|
|
output_name: APP_NAME,
|
|
export_method: "developer-id",
|
|
export_options: {
|
|
provisioningProfiles: {
|
|
"#{DEVELOPER_APP_IDENTIFIER}" => "match Direct #{DEVELOPER_APP_IDENTIFIER} macos"
|
|
}
|
|
}
|
|
)
|
|
|
|
notarize(
|
|
package: "fastlane/builds/#{version}-#{build}/macOS/#{APP_NAME}.app",
|
|
bundle_id: "#{DEVELOPER_APP_IDENTIFIER}",
|
|
api_key: api_key,
|
|
print_log: true
|
|
)
|
|
|
|
# Staple the notarization ticket so Gatekeeper doesn't need to phone home
|
|
# when the user first launches from Sparkle/DMG.
|
|
sh "xcrun stapler staple 'fastlane/builds/#{version}-#{build}/macOS/#{APP_NAME}.app'"
|
|
|
|
# Package the stapled .app into a .zip (Sparkle) and .dmg (Homebrew / direct).
|
|
dir = "fastlane/builds/#{version}-#{build}/macOS"
|
|
app = "#{dir}/#{APP_NAME}.app"
|
|
zip = "#{dir}/#{APP_NAME}-#{version}-macOS.zip"
|
|
dmg = "#{dir}/#{APP_NAME}-#{version}-macOS.dmg"
|
|
|
|
# .zip: `ditto -c -k --keepParent` is Sparkle's expected archive layout
|
|
# (preserves the .app wrapper, resource forks, symlinks).
|
|
sh "/usr/bin/ditto -c -k --keepParent '#{app}' '#{zip}'"
|
|
|
|
# .dmg: read-only UDZO, overwrites any stale image from previous runs.
|
|
sh "/bin/rm -f '#{dmg}'"
|
|
sh "/usr/bin/hdiutil create -volname '#{APP_NAME} #{version}' -srcfolder '#{app}' -ov -format UDZO '#{dmg}'"
|
|
end
|
|
end
|