class Unlimit::CLI

Public Instance Methods

putsWithOverrides(parameter, value, flag) click to toggle source
# File lib/unlimit.rb, line 29
def putsWithOverrides(parameter, value, flag)
  puts "Using #{parameter}: #{value}".green + " (Use flag --#{flag} to override)".yellow
end
sendEvent(event) click to toggle source
# File lib/unlimit.rb, line 296
def sendEvent(event)
  begin
  uri = URI("https://curl.press/api/unlimit/add?event=#{event}")
  Net::HTTP.get(uri)
  rescue StandardError
  end
end
showVersion() click to toggle source
# File lib/unlimit.rb, line 33
def showVersion
  puts Divider
  puts "You're using unlimit version #{Unlimit::VERSION}".green
  puts 'https://github.com/biocross/unlimit'.yellow
  puts Divider
  abort
end
start(options, using_bundler, raven) click to toggle source
# File lib/unlimit.rb, line 41
def start(options, using_bundler, raven)
  puts Divider
  puts "            unlimit 🚀📲 (#{Unlimit::VERSION})       "
  puts '    Switching your project to Personal Team!    '.yellow
  puts Divider

  xcode_project_files = Dir.glob('*.xcodeproj')
  project_path = ''
  plist_path = ''
  target_name = ''
  personal_team_id = ''
  uses_app_groups = false
  app_group_name = ''
  entitlements_file = ''
  project_configuration_file = ''
  extensions = []
  target = nil
  session_uuid = SecureRandom.uuid
  fastlane_command = using_bundler ? 'bundle exec fastlane' : 'fastlane'

  cli = HighLine.new
  sendEvent('unlimit.start')
  raven.capture_message('Begin', level: 'info')

  # Check for a valid xcode_project
  if xcode_project_files.count == 1 || options.key?(ProjectPathKey)
    project_path = if options.key?(ProjectPathKey)
                     options[ProjectPathKey]
                   else
                     xcode_project_files.first
                   end

    unless File.directory?(project_path)
      abort("Project not found at #{project_path}".red)
    end
    putsWithOverrides('project', project_path, ProjectPathKey)
  else
    abort('Please specify the .xcodeproj project file to use with the --project option like --project MyProject.xcodeproj'.red)
  end

  project = Xcodeproj::Project.open(project_path)

  if options.key?(TargetNameKey)
    target_name = options[TargetNameKey]
    target = project.targets.find { |t| t.name == target_name }
    abort "Couldn't find the target '#{target_name}'  in '#{project_path}'" if target.nil?
    puts "Using target #{target_name}"
  else
    project.targets.each do |current_target|
      next unless current_target.product_type == ProductTypeApplicationTarget

      target = current_target
      target_name = current_target.name
      putsWithOverrides('target', target_name, TargetNameKey)

      # Remove Fabric Script build phase to stop the annoying "new app" email
      unless options.key?(KeepFabricKey)
        current_target.build_phases.each do |build_phase|
          next unless build_phase.is_a?(Xcodeproj::Project::Object::PBXShellScriptBuildPhase) && build_phase.shell_script.include?('/Fabric/run')

          build_phase.shell_script = '#' + build_phase.shell_script
        end
      end
      break
    end
  end

  if options.key?(PlistPathKey)
    plist_path = options[PlistPathKey]

    unless File.file?(plist_path)
      abort("Info.plist file not found at path: #{plist_path}".red)
    end
    puts "Using Info.plist at path #{plist_path}.".green
  else
    if target.build_configurations.count.positive?
      build_settings = target.build_configurations.first.build_settings
      plist_path = build_settings[InfoPlistBuildSettingKey]
      putsWithOverrides('Info.plist', plist_path, PlistPathKey)
    end

    if plist_path.nil? || plist_path.empty?
      abort('Please specify the path to your main target\'s Info.plist file with the --plist option like --plist MyProject-Info.plist'.red)
    end
  end

  if options.key?(ProjectConfigurationFileKey)
    project_configuration_file = options[ProjectConfigurationFileKey]

    unless File.file?(project_configuration_file)
      abort("YAML Configuration file not found at path: #{project_configuration_file}".red)
    end
    puts "Using YAML Configuration at path #{project_configuration_file}.".green
  else
    project_configuration_file = DefaultProjectConfigurationFilePath
    putsWithOverrides('YAML Configuration', project_configuration_file, ProjectConfigurationFileKey) if File.file?(project_configuration_file)
  end

  project.targets.each do |target|
    if target.product_type.include? 'app-extension'
      extensions.push(target.name)
    end
  end

  if options.key?(TeamIDKey)
    personal_team_id = options[TeamIDKey]
    putsWithOverrides('Team ID', personal_team_id, TeamIDKey)
  else
    valid_codesigning_identities, stderr, status = Open3.capture3('security find-identity -p codesigning -v')
    personal_teams = valid_codesigning_identities.scan(/\"(.+)\"/i)
    if personal_teams.size == 1
      personal_team_name = personal_teams.first
      if personal_team_name.is_a?(Array)
        personal_team_name = personal_team_name.first
      end
      personal_team_name = personal_team_name.strip
      personal_team_id, stderr, status = Open3.capture3("security find-certificate -c \"#{personal_team_name}\" -p | openssl x509 -noout -subject")
      personal_team_id = personal_team_id.split("/")
      personal_team_id.each do |value|
        if value.include?("OU=")
          personal_team_id = value.gsub("OU=", "").strip
          break
        end
      end
    elsif personal_teams.size == 0
      puts "No valid codesigning identities found on your Mac. Please open Xcode, login into your account (Preferences > Accounts) and download your identities.".red
      abort()
    else
      puts "\nYou have quite a few developer identities on your machine. unlimit is unable to decide which one to use 😅".yellow
      puts 'If you know the Team ID to use, pass it with the --teamid flag like --teamid 6A2T6455Y3'.yellow
      puts "\nFor now, choose one from the list below: (Your personal team most likely contains your email or full name)".yellow
      puts 'Which codesigning identity should unlimit use?'.green
      selected_team = ''
      codesigning_options = valid_codesigning_identities.split("\n")
      codesigning_options = codesigning_options.select { |line| line.include?(')') }
      codesigning_options = codesigning_options.map { |identity| identity.scan(/\d+\) (.+)/).first.first }
      cli.choose do |menu|
        menu.prompt = 'Select one by entering the number: '
        codesigning_options.each do |identity|
          menu.choice(identity) { selected_team = identity }
        end
      end

      personal_team = selected_team.scan(/:(.+)\(.+\)\"/i)
      personal_team_name = personal_team.first.first.strip
      personal_team_id, stderr, status = Open3.capture3("security find-certificate -c \"#{personal_team_name}\" -p | openssl x509 -text | grep -o OU=[^,]* | grep -v Apple | sed s/OU=//g")
      personal_team_id = personal_team_id.strip
    end
    putsWithOverrides("Team ID (from: #{personal_team_name})", personal_team_id, TeamIDKey)
  end

  puts "#{Divider}\n"

  # Turn off capabilities that require entitlements
  puts 'Turning OFF all Capabilities'.red
  project.root_object.attributes.each do |value|
    next unless value[0] == 'TargetAttributes'

    hash = value[1]
    hash.each do |_key, val|
      next unless val.key?('SystemCapabilities')

      capabilities = val['SystemCapabilities']
      capabilities.each do |key, val|
        next unless val.key?('enabled')

        if key.include?('com.apple.ApplicationGroups.iOS')
          uses_app_groups = true
        end

        unless CapabilitiesWhitelist.include?(key)
          puts ' Turning OFF ' + key
          capabilities[key]['enabled'] = '0'
        end
      end
    end
  end
  project.save

  # Remove Entitlements
  puts 'Clearing entitlements...'.red
  Dir.glob('**/*.entitlements').each do |source_file|
    entitlements_plist = uses_app_groups ? { "com.apple.security.application-groups": '' }.to_plist : {}.to_plist
    entitlements_file = source_file if uses_app_groups
    File.open(source_file, 'w') { |file| file.puts entitlements_plist }
  end

  # Remove Capability Keys from Plist
  puts 'Removing capabilities from Info.plist...'.red
  info_plist = Plist.parse_xml(plist_path)
  info_plist.delete('UIBackgroundModes')
  File.open(plist_path, 'w') { |file| file.puts info_plist.to_plist }

  # Change Bundle Identifier
  puts 'Changing bundle identifier...'.red
  bundle_identifier = "com.unlimit.#{session_uuid}"
  system("#{FastlaneEnvironmentVariables} #{fastlane_command} run update_app_identifier plist_path:#{plist_path} app_identifier:#{bundle_identifier}")

  if uses_app_groups # Create a temporary fastfile, and set the app group identifiers
    app_group_name = "group.#{bundle_identifier}"
    fastfile = "lane :set_app_group do
    update_app_group_identifiers(entitlements_file: \"#{entitlements_file}\", app_group_identifiers: ['#{app_group_name}'])
    end"

    existing_fastfile = ''
    if File.file?(FastFilePath)
      File.open('./fastlane/Fastfile', 'r') do |file|
        existing_fastfile = file.read
      end
    end
    File.open(FastFilePath, 'w') do |file|
      file.write(fastfile)
    end
    system("#{FastlaneEnvironmentVariables} #{fastlane_command} fastlane set_app_group")
    existing_fastfile.empty? ? File.delete(FastFilePath) : File.open('./fastlane/Fastfile', 'w') { |file| file.write(existing_fastfile) }
  end

  puts 'Enabling Automatic Code Signing...'.red
  system("#{FastlaneEnvironmentVariables} #{fastlane_command} fastlane run automatic_code_signing use_automatic_signing:true targets:#{target_name}")

  puts 'Switching to Personal Team...'.red
  system("#{FastlaneEnvironmentVariables} #{fastlane_command} fastlane run update_project_team teamid:\"#{personal_team_id}\" targets:#{target_name}")

  # Remove App Extensions
  unless extensions.empty?
    app_extensions = extensions.join(', ')
    puts "Removing App Extensions: #{app_extensions}".red
    system("#{'bundle exec' if using_bundler} configure_extensions remove #{project_path} #{target_name} #{app_extensions}")
  end

  if File.file?(project_configuration_file)
    puts "Running Custom Scripts from #{project_configuration_file}".red
    local_configuration = YAML.load_file(project_configuration_file)
    unless local_configuration['custom_scripts'].empty?
      environment_variables = { 'UNLIMIT_PROJECT_PATH' => project_path, 'UNLIMIT_TARGET_NAME' => target_name, 'UNLIMIT_PLIST_PATH' => plist_path, 'UNLIMIT_TEAM_ID' => personal_team_id, 'UNLIMIT_APP_BUNDLE_ID' => bundle_identifier, 'UNLIMIT_APP_GROUP_NAME' => app_group_name }
      local_configuration['custom_scripts'].each do |script|
        script = script.to_s
        environment_variables.each do |key, variable|
          script.gsub!(key.to_s, variable.to_s)
        end
        puts "Running: #{script}".green
        output, stderr, status = Open3.capture3(script)
        puts output unless output.empty?
        puts stderr unless stderr.empty?
        puts "Done with Status: #{status}"
      end
    end
  end
  sendEvent('unlimit.finish')
  raven.capture_message('Finished', level: 'info')
  puts "\n#{Divider}"
  puts 'You\'re good to go! Just connect your device and hit run!'.green
  puts Divider
end