Files
TREK/client/ios/scripts/add-extension.rb
T
Maurice 7b8bd4b166 feat(native): TREK in the share sheet, with the trip picked right there
A share extension puts TREK in the share sheet of Mail, Safari, Files, Wallet
and any app that shares a document, a link, text or a photo. It shows what came
in (a Wallet pass, a PDF, a mail, the page as Safari rendered it, text, a photo)
and the user's trips with their covers, the next one already picked, and hands
both to the app, which opens that trip's booking import with the file in place.
"Choose later" leaves the trip question to the app's import sheet as before.

- ShareExtension target (add-extension.rb learns "share": JavaScript and the
  localizations go in as resources), bundle com.liketrek.trek.share, signed in
  the release lane, its plists linted in native.yml
- Safari hands over the rendered page (SharePage.js), so a confirmation behind
  a sign-in arrives whole; other apps' links are fetched
- The item travels through the shared keychain group the widgets already use;
  the app takes it on com.liketrek.trek://share or when it next becomes active
- Feature shareSheet; strings in the app's 28 languages
2026-11-07 11:25:18 +01:00

91 lines
3.9 KiB
Ruby

# Adds an app extension target to App.xcodeproj and embeds it in the app.
#
# ruby ios/scripts/add-extension.rb <name> <bundle-id> <type> [extra frameworks...]
#
# type is "notification" (Notification Service Extension), "widget" (WidgetKit,
# which also carries the Live Activities) or "share" (the share sheet entry). The extension's sources live in
# ios/App/<name>/ with an Info.plist and <name>.entitlements next to them. The
# project file stays checked in, so the targets show in Xcode like any other;
# this script is how they got there, and running it again changes nothing.
require 'xcodeproj'
name, bundle_id, type, *frameworks = ARGV
abort 'usage: add-extension.rb <name> <bundle-id> <notification|widget|share> [frameworks...]' unless name && bundle_id && type
project_path = File.expand_path('../App/App.xcodeproj', __dir__)
project = Xcodeproj::Project.open(project_path)
app = project.targets.find { |t| t.name == 'App' } or abort 'no App target'
if project.targets.any? { |t| t.name == name }
puts "#{name} is already in the project"
exit 0
end
target = project.new_target(:app_extension, name, :ios, '15.0', nil, :swift)
# new_target links Foundation by a path into one SDK version; it is linked
# implicitly anyway, and a pinned SDK path breaks on the next Xcode.
target.frameworks_build_phase.files.select { |f| f.display_name.to_s.include?('Foundation') }.each do |f|
ref = f.file_ref
f.remove_from_project
ref&.remove_from_project
end
group = project.main_group.find_subpath(name, true)
group.set_source_tree('<group>')
group.set_path(name)
sources = Dir.children(File.expand_path("../App/#{name}", __dir__)).sort
# Localizations go in as one variant group, so Xcode copies each language's
# strings into the extension bundle; JavaScript (Safari's page preprocessing) is
# a resource as well.
lprojs, files = sources.partition { |file| file.end_with?('.lproj') }
files.each do |file|
ref = group.new_reference(file)
target.add_file_references([ref]) if file.end_with?('.swift')
target.add_resources([ref]) if file.end_with?('.js')
end
unless lprojs.empty?
variant = group.new_variant_group('Localizable.strings')
lprojs.each do |dir|
lang = dir.delete_suffix('.lproj')
ref = variant.new_reference("#{dir}/Localizable.strings")
ref.name = lang
ref.last_known_file_type = 'text.plist.strings'
project.root_object.known_regions |= [lang]
end
target.add_resources([variant])
end
frameworks.each do |framework|
ref = project.frameworks_group.new_file("System/Library/Frameworks/#{framework}.framework", :sdk_root)
target.frameworks_build_phase.add_file_reference(ref, true)
end
app_settings = app.build_configurations.first.build_settings
target.build_configurations.each do |config|
s = config.build_settings
s['PRODUCT_BUNDLE_IDENTIFIER'] = bundle_id
s['PRODUCT_NAME'] = '$(TARGET_NAME)'
s['INFOPLIST_FILE'] = "#{name}/Info.plist"
s['GENERATE_INFOPLIST_FILE'] = 'NO'
s['CODE_SIGN_ENTITLEMENTS'] = "#{name}/#{name}.entitlements"
s['CODE_SIGN_STYLE'] = 'Automatic'
s['DEVELOPMENT_TEAM'] = app_settings['DEVELOPMENT_TEAM'] if app_settings['DEVELOPMENT_TEAM']
s['SWIFT_VERSION'] = '5.0'
s['TARGETED_DEVICE_FAMILY'] = '1,2'
s['IPHONEOS_DEPLOYMENT_TARGET'] = type == 'widget' ? '16.2' : '15.0'
s['MARKETING_VERSION'] = app_settings['MARKETING_VERSION'] || '1.0'
s['CURRENT_PROJECT_VERSION'] = app_settings['CURRENT_PROJECT_VERSION'] || '1'
s['SKIP_INSTALL'] = 'YES'
s['LD_RUNPATH_SEARCH_PATHS'] = ['$(inherited)', '@executable_path/Frameworks', '@executable_path/../../Frameworks']
end
embed = app.copy_files_build_phases.find { |p| p.name == 'Embed Foundation Extensions' } ||
app.new_copy_files_build_phase('Embed Foundation Extensions')
embed.symbol_dst_subfolder_spec = :plug_ins
build_file = embed.add_file_reference(target.product_reference, true)
build_file.settings = { 'ATTRIBUTES' => ['RemoveHeadersOnCopy'] }
app.add_dependency(target)
project.save
puts "added #{name} (#{bundle_id})"