Full Code of XcodesOrg/XcodesApp for AI

main 1a0d3353b9a1 cached
213 files
1.4 MB
362.4k tokens
3 symbols
1 requests
Download .txt
Showing preview only (1,474K chars total). Download the full file or copy to clipboard to get everything.
Repository: XcodesOrg/XcodesApp
Branch: main
Commit: 1a0d3353b9a1
Files: 213
Total size: 1.4 MB

Directory structure:
gitextract_lwe5ux5j/

├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── dependabot.yml
│   ├── release-drafter.yml
│   └── workflows/
│       ├── appcast.yml
│       ├── ci.yml
│       ├── release-drafter.yml
│       └── xcstrings.yml
├── .gitignore
├── AppCast/
│   ├── .gitignore
│   ├── Gemfile
│   ├── _config.yml
│   ├── _includes/
│   │   └── appcast.inc
│   ├── _plugins/
│   │   └── signature_filter.rb
│   ├── appcast.xml
│   └── appcast_pre.xml
├── CONTRIBUTING.md
├── DECISIONS.md
├── HelperXPCShared/
│   └── HelperXPCShared.swift
├── LICENSE
├── README.md
├── Scripts/
│   ├── export_options.plist
│   ├── fix_libfido2_framework.sh
│   ├── increment_build_number.sh
│   ├── notarize.sh
│   ├── package_release.sh
│   ├── sign_update
│   └── uninstall_privileged_helper.sh
├── Xcodes/
│   ├── AcknowledgementsGenerator/
│   │   ├── .gitignore
│   │   ├── .swiftpm/
│   │   │   └── xcode/
│   │   │       └── package.xcworkspace/
│   │   │           └── contents.xcworkspacedata
│   │   ├── Package.swift
│   │   ├── README.md
│   │   ├── Sources/
│   │   │   └── AcknowledgementsGenerator/
│   │   │       ├── Extensions/
│   │   │       │   ├── CollectionExtensions.swift
│   │   │       │   └── StringExtensions.swift
│   │   │       ├── Tools/
│   │   │       │   └── Xcode.swift
│   │   │       └── main.swift
│   │   └── spm-licenses.LICENSE
│   ├── AppleAPI/
│   │   ├── .gitignore
│   │   ├── Package.swift
│   │   ├── README.md
│   │   ├── Sources/
│   │   │   └── AppleAPI/
│   │   │       ├── Client.swift
│   │   │       ├── Environment.swift
│   │   │       ├── Hashcash.swift
│   │   │       └── URLRequest+Apple.swift
│   │   └── Tests/
│   │       ├── AppleAPITests/
│   │       │   ├── AppleAPITests.swift
│   │       │   └── XCTestManifests.swift
│   │       └── LinuxMain.swift
│   ├── Backend/
│   │   ├── AppState+Install.swift
│   │   ├── AppState+Runtimes.swift
│   │   ├── AppState+Update.swift
│   │   ├── AppState.swift
│   │   ├── Aria2CError.swift
│   │   ├── AvailableXcode.swift
│   │   ├── Bundle+InfoPlistValues.swift
│   │   ├── Collection+.swift
│   │   ├── Configure.swift
│   │   ├── DataSource.swift
│   │   ├── DateFormatter+.swift
│   │   ├── Downloader.swift
│   │   ├── Downloads.swift
│   │   ├── Entry+.swift
│   │   ├── Environment.swift
│   │   ├── FileError.swift
│   │   ├── FileManager+.swift
│   │   ├── FocusedValues.swift
│   │   ├── Foundation.swift
│   │   ├── Hardware.swift
│   │   ├── HelperClient.swift
│   │   ├── HelperInstallState.swift
│   │   ├── InstalledXcode.swift
│   │   ├── IsTesting.swift
│   │   ├── NotificationManager.swift
│   │   ├── Optional+IsNotNil.swift
│   │   ├── Path+.swift
│   │   ├── Process.swift
│   │   ├── Progress+.swift
│   │   ├── Publisher+Resumable.swift
│   │   ├── SDKs+Xcode.swift
│   │   ├── SelectedActionType.swift
│   │   ├── SelectedXcode.swift
│   │   ├── URLRequest+Apple.swift
│   │   ├── URLSession+DownloadTaskPublisher.swift
│   │   ├── Version+.swift
│   │   ├── Version+Xcode.swift
│   │   ├── Version+XcodeReleases.swift
│   │   ├── Xcode.swift
│   │   ├── XcodeCommands.swift
│   │   └── XcodeInstallState.swift
│   ├── Frontend/
│   │   ├── About/
│   │   │   ├── AboutView.swift
│   │   │   ├── AcknowledgementsView.swift
│   │   │   └── ScrollingTextView.swift
│   │   ├── Common/
│   │   │   ├── NavigationSplitViewWrapper.swift
│   │   │   ├── ObservingProgressIndicator.swift
│   │   │   ├── ProgressButton.swift
│   │   │   ├── ProgressIndicator.swift
│   │   │   ├── TagView.swift
│   │   │   ├── TrailingIconLabelStyle.swift
│   │   │   ├── XcodesAlert.swift
│   │   │   └── XcodesSheet.swift
│   │   ├── InfoPane/
│   │   │   ├── CompatibilityView.swift
│   │   │   ├── CompilersView.swift
│   │   │   ├── CornerRadiusModifier.swift
│   │   │   ├── IconView.swift
│   │   │   ├── IdenticalBuildView.swift
│   │   │   ├── InfoPane.swift
│   │   │   ├── InfoPaneControls.swift
│   │   │   ├── InstallationStepDetailView.swift
│   │   │   ├── InstalledStateButtons.swift
│   │   │   ├── NotInstalledStateButtons.swift
│   │   │   ├── PlatformsView.swift
│   │   │   ├── ReleaseDateView.swift
│   │   │   ├── ReleaseNotesView.swift
│   │   │   ├── RuntimeInstallationStepDetailView.swift
│   │   │   ├── SDKsView.swift
│   │   │   └── UnselectedView.swift
│   │   ├── MainWindow.swift
│   │   ├── Preferences/
│   │   │   ├── AdvancedPreferencePane.swift
│   │   │   ├── DownloadPreferencePane.swift
│   │   │   ├── ExperiementsPreferencePane.swift
│   │   │   ├── GeneralPreferencePane.swift
│   │   │   ├── NotificationsView.swift
│   │   │   ├── PlatformsListView.swift
│   │   │   ├── PreferencesView.swift
│   │   │   └── UpdatesPreferencePane.swift
│   │   ├── SignIn/
│   │   │   ├── AttributedText.swift
│   │   │   ├── NSAttributedString+.swift
│   │   │   ├── PinCodeTextView.swift
│   │   │   ├── SignIn2FAView.swift
│   │   │   ├── SignInCredentialsView.swift
│   │   │   ├── SignInPhoneListView.swift
│   │   │   ├── SignInSMSView.swift
│   │   │   ├── SignInSecurityKeyPinView.swift
│   │   │   ├── SignInSecurityKeyTouchView.swift
│   │   │   └── SignedInView.swift
│   │   ├── View+Conditional.swift
│   │   ├── View+IsHidden.swift
│   │   └── XcodeList/
│   │       ├── AppStoreButtonStyle.swift
│   │       ├── BottomStatusBar.swift
│   │       ├── InstallationStepRowView.swift
│   │       ├── MainToolbar.swift
│   │       ├── Tag.swift
│   │       ├── XcodeListCategory.swift
│   │       ├── XcodeListView.swift
│   │       └── XcodeListViewRow.swift
│   ├── Preview Content/
│   │   └── Preview Assets.xcassets/
│   │       └── Contents.json
│   ├── Resources/
│   │   ├── Assets.xcassets/
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   ├── Contents.json
│   │   │   ├── Icons/
│   │   │   │   └── Contents.json
│   │   │   ├── install.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── xcode-beta.imageset/
│   │   │   │   └── Contents.json
│   │   │   └── xcode.imageset/
│   │   │       └── Contents.json
│   │   ├── Info.plist
│   │   ├── Licenses.rtf
│   │   ├── Localizable.xcstrings
│   │   ├── Xcodes.entitlements
│   │   ├── XcodesIcon.icon/
│   │   │   └── icon.json
│   │   ├── XcodesTest.entitlements
│   │   ├── aria2c
│   │   ├── aria2c.LICENSE
│   │   └── unxip
│   ├── XcodesApp.swift
│   └── XcodesKit/
│       ├── .gitignore
│       ├── Package.swift
│       ├── README.md
│       ├── Sources/
│       │   └── XcodesKit/
│       │       ├── Extensions/
│       │       │   ├── Foundation.swift
│       │       │   └── Logger.swift
│       │       ├── Models/
│       │       │   ├── Runtimes/
│       │       │   │   ├── CoreSimulatorImage.swift
│       │       │   │   ├── RuntimeInstallState.swift
│       │       │   │   ├── RuntimeInstallationStep.swift
│       │       │   │   └── Runtimes.swift
│       │       │   ├── XcodeInstallState.swift
│       │       │   ├── XcodeInstallationStep.swift
│       │       │   └── XcodeReleases/
│       │       │       ├── Architecture.swift
│       │       │       ├── Checksums.swift
│       │       │       ├── Compilers.swift
│       │       │       ├── Link.swift
│       │       │       ├── Release.swift
│       │       │       ├── SDKs.swift
│       │       │       ├── XcodeRelease.swift
│       │       │       ├── XcodeVersion.swift
│       │       │       └── YMD.swift
│       │       ├── Services/
│       │       │   └── RuntimeService.swift
│       │       ├── Shell/
│       │       │   ├── Process.swift
│       │       │   └── XcodesShell.swift
│       │       └── XcodesKitEnvironment.swift
│       └── Tests/
│           └── XcodesKitTests/
│               └── XcodesKitTests.swift
├── Xcodes.xcodeproj/
│   ├── ._project.xcworkspace
│   ├── project.pbxproj
│   ├── project.xcworkspace/
│   │   ├── contents.xcworkspacedata
│   │   └── xcshareddata/
│   │       ├── IDEWorkspaceChecks.plist
│   │       └── swiftpm/
│   │           └── Package.resolved
│   └── xcshareddata/
│       └── xcschemes/
│           ├── Xcodes.xcscheme
│           └── com.robotsandpencils.XcodesApp.Helper.xcscheme
├── XcodesTests/
│   ├── AppStateTests.swift
│   ├── AppStateUpdateTests.swift
│   ├── Bundle+XcodesTests.swift
│   ├── Environment+Mock.swift
│   ├── Fixtures/
│   │   ├── Stub-0.0.0.Info.plist
│   │   └── Stub-version.plist
│   └── Info.plist
├── com.xcodesorg.xcodesapp.Helper/
│   ├── AuditTokenHack.h
│   ├── AuditTokenHack.m
│   ├── ConnectionVerifier.swift
│   ├── Info.plist
│   ├── Logger.swift
│   ├── SimpleXPCApp.LICENSE
│   ├── XPCDelegate.swift
│   ├── com.xcodesorg.xcodesapp.Helper-Bridging-Header.h
│   ├── com.xcodesorg.xcodesapp.HelperTest.entitlements
│   ├── launchd.plist
│   └── main.swift
└── nextstep.md

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/CODEOWNERS
================================================
*   @RobotsAndPencils/xcodes


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Version**
 - OS: 
 - Xcodes:


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''

---

**Tell us how we can improve Xcodes**

**Is your feature request related to a problem? Please describe.**

**What would you like to see? How would you like it to work?**


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  # Maintain dependencies for GitHub Actions
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "daily"


================================================
FILE: .github/release-drafter.yml
================================================
categories:
  - title: '🚀 Enhancements'
    labels:
      - 'enhancement'
  - title: '🐛 Bug Fixes'
    labels:
      - 'bugfix'
  - title: '🌎 Localization'
    labels:
      - 'localization'
  - title: '🧰 Maintenance'
    label:
      - 'chore'
      - 'documentation'
      - 'dependencies'
template: |
  Install Xcodes using one of the methods listed [here](https://github.com/RobotsAndPencils/XcodesApp#installation).
  Update Xcodes by selecting Check for Updates... in the Xcodes menu in the menu bar.

  ## Changes

  $CHANGES


================================================
FILE: .github/workflows/appcast.yml
================================================
name: Build and publish a new appcast file

on:
  workflow_dispatch:
  release:

jobs:
  jekyll:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout 🛎
      uses: actions/checkout@v4
      with:
        # If you're using actions/checkout@v4 you must set persist-credentials to false in most cases for the deployment to work correctly.
        persist-credentials: false

#    - name: Cache 📦
#      uses: actions/cache@v4.1.1
#      with:
#        path: AppCast/vendor/bundle
#        key: ${{ runner.os }}-gems-v1.0-${{ hashFiles('AppCast/Gemfile') }}
#        restore-keys: |
#          ${{ runner.os }}-gems-
          
    - name: Setup Ruby, JRuby and TruffleRuby
      uses: ruby/setup-ruby@v1.197.0
      with:
        ruby-version: '3.0'

    - name: Bundler 💎
      working-directory: AppCast
      env:
        BUNDLE_PATH: vendor/bundle
      run: |
        gem install bundler
        bundle install

    - name: Build 🛠
      working-directory: AppCast
      env:
        BUNDLE_PATH: vendor/bundle
        JEKYLL_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: bundle exec jekyll build
      
    - name: Publish 🚀
      uses: JamesIves/github-pages-deploy-action@releases/v4
      with:
        token: ${{ secrets.GITHUB_TOKEN }}
        branch: gh-pages
        folder: AppCast/_site


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  test:
    runs-on: macos-15
    steps:
    - uses: actions/checkout@v4
    - name: Run tests
      env:
        DEVELOPER_DIR: /Applications/Xcode_16.4.app
      run: xcodebuild test -scheme Xcodes


================================================
FILE: .github/workflows/release-drafter.yml
================================================
name: Release Drafter

on:
  # Allow running it manually in case we forget to label a PR before merging
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  update_release_draft:
    runs-on: ubuntu-latest
    steps:
      - uses: release-drafter/release-drafter@v6
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/xcstrings.yml
================================================
name: XCStrings Validation

on:
  workflow_dispatch:
  push:
    branches:
      - main
  pull_request:

jobs:
  test:
    runs-on: macos-13
    steps:
    - uses: actions/checkout@v4

    - name: Clone SwiftPolyglot
      run: git clone --branch 0.3.1 -- https://github.com/appdecostudio/SwiftPolyglot.git ../SwiftPolyglot
      
    - name: validate translations
      run: |
        swift build --package-path ../SwiftPolyglot --configuration release
        swift run --package-path ../SwiftPolyglot swiftpolyglot "ca,de,el,es,fi,fr,hi,it,ja,ko,nl,pl,pt-BR,ru,tr,uk,zh-Hans,zh-Hant" --errorOnMissing


================================================
FILE: .gitignore
================================================
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## User settings
xcuserdata/

## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout

## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3

## Obj-C/Swift specific
*.hmap

## App packaging
*.ipa
*.dSYM.zip
*.dSYM
Archive/*
Product/*

## Playgrounds
timeline.xctimeline
playground.xcworkspace

# Jetbrains IDEA
.idea

# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm

.build/

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts

Carthage/Build/

# Accio dependency management
Dependencies/
.accio/

# fastlane
#
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control

fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output

# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode

iOSInjectionProject/

.DS_Store


================================================
FILE: AppCast/.gitignore
================================================
_site
.sass-cache
.jekyll-metadata


================================================
FILE: AppCast/Gemfile
================================================
source "https://rubygems.org"

# Hello! This is where you manage which Jekyll version is used to run.
# When you want to use a different version, change it below, save the
# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
#
#     bundle exec jekyll serve
#
# This will help ensure the proper Jekyll version is running.
# Happy Jekylling!
gem "jekyll", "~> 4.4.1"

gem "jekyll-github-metadata", group: :jekyll_plugins

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
# and associated library.
install_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do
  gem "tzinfo", "~> 1.2"
  gem "tzinfo-data"
end

# Performance-booster for watching directories on Windows
gem "wdm", "~> 0.1.0", :install_if => Gem.win_platform?

# kramdown v2 ships without the gfm parser by default. If you're using
# kramdown v1, comment out this line.
gem "kramdown-parser-gfm"



================================================
FILE: AppCast/_config.yml
================================================
# Welcome to Jekyll!
#
# This config file is meant for settings that affect your whole blog, values
# which you are expected to set up once and rarely edit after that. If you find
# yourself editing this file very often, consider using Jekyll's data files
# feature for the data you need to update frequently.
#
# For technical reasons, this file is *NOT* reloaded automatically when you use
# 'bundle exec jekyll serve'. If you change this file, please restart the server process.

# Site settings
# These are used to personalize your new site. If you look in the HTML files,
# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
# You can create any custom variable you would like, and they will be accessible
# in the templates via {{ site.myvariable }}.
title: Xcodes.app
description: >- # this means to ignore newlines until "baseurl:"
baseurl: "" # the subpath of your site, e.g. /blog
url: "" # the base hostname & protocol for your site, e.g. http://example.com

# Build settings
markdown: kramdown
plugins:
  - "jekyll-github-metadata"

# Exclude from processing.
# The following items will not be processed, by default. Create a custom list
# to override the default setting.
# exclude:
#   - Gemfile
#   - Gemfile.lock
#   - node_modules
#   - vendor/bundle/
#   - vendor/cache/
#   - vendor/gems/
#   - vendor/ruby/


================================================
FILE: AppCast/_includes/appcast.inc
================================================
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/">
    <channel>
        <title>{{ site.github.project_title }}</title>
        <description>Most recent changes with links to updates.</description>
        <language>en</language>
        {% for release in site.github.releases %}
            {% unless release.draft %}
                {% unless release.prerelease and page.release_only %}
                    <item>
                        <title>{{ release.name }}</title>
                        <description><![CDATA[{{ release.body | markdownify }}]]></description>
                        <pubDate>{{ release.published_at | date_to_rfc822 }}</pubDate>
                        {% for asset in release.assets limit:1 %}
                            {% assign signature = release.body | sparkle_signature %}

                            {% assign build_nums = release.tag_name | replace_first:'v','' | replace_first:'b',',' | split:',' %}
                            {% if build_nums.size == 2 %}
                                {% assign version_number = build_nums[0] %}
                                {% assign build_number = build_nums[1] %}

                                <enclosure
                                    url="{{ asset.browser_download_url }}"
                                    sparkle:version="{{ build_number }}"
                                    sparkle:shortVersionString="{{ version_number }}"
                                    sparkle:edSignature="{{ signature }}"
                                    length="{{ asset.size }}"
                                    type="application/octet-stream" />
                            {% else %}
                                {% assign version = release.tag_name | remove_first:'v' %}

                                <enclosure
                                    url="{{ asset.browser_download_url }}"
                                    sparkle:version="{{ version }}"
                                    sparkle:edSignature="{{ signature }}"
                                    length="{{ asset.size }}"
                                    type="application/octet-stream" />
                            {% endif %}
                        {% endfor %}
                    </item>
                {% endunless %}
            {% endunless %}
        {% endfor %}
    </channel>
</rss>


================================================
FILE: AppCast/_plugins/signature_filter.rb
================================================
module Jekyll
  module SignatureFilter
    def sparkle_signature(release_body)
      regex = /<!-- sparkle:edSignature=(?<signature>.*) -->/m
      signature = release_body.match(regex).named_captures["signature"]
      raise "Didn't find a signature in the release body." if signature.empty?
      signature
    end
  end
end

Liquid::Template.register_filter(Jekyll::SignatureFilter)

================================================
FILE: AppCast/appcast.xml
================================================
---
release_only: true
---
{%include appcast.inc %}


================================================
FILE: AppCast/appcast_pre.xml
================================================
---
release_only: false
---
{%include appcast.inc %}


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Xcodes
We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:

- Reporting a bug
- Discussing the current state of the code
- Submitting a fix
- Proposing new features
- Becoming a maintainer

## We Develop with GitHub
We use GitHub to host code, to track issues and feature requests, as well as accept pull requests.

## All Code Changes Happen Through Pull Requests
Pull requests are the best way to propose changes to the codebase  We actively welcome your pull requests:

1. Fork the repo and create your branch from `main`.
2. If you've added code that should be tested, add tests.
3. If you've added new functionality, add documentation 
4. Ensure the test suite passes.
5. Make sure your code lints.
6. Issue that pull request!

## Any contributions you make will be under the MIT Software License
In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern.

## Report bugs using GitHub [issues](https://github.com/robotsandpencils/xcodesapp/issues)
We use GitHub issues to track public bugs. Report a bug by [opening a new issue](); it's that easy!

## Write bug reports with detail, background, and sample code

**Great Bug Reports** tend to have:

- A quick summary and/or background
- Steps to reproduce
  - Be specific!
- What you expected would happen
- What actually happens
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)

People *love* thorough bug reports.

## License
By contributing, you agree that your contributions will be licensed under its MIT License.


================================================
FILE: DECISIONS.md
================================================
# Decisions

This file exists to provide a historical record of the motivation for important technical decisions in the project. It's inspired by Architectural Decision Records, but the implementation is intentionally simpler than usual. When a new decision is made, append it to the end of the file with a header. Decisions can be changed later. This is a reflection of real life, not a contract that has to be followed.

## Why Make Xcodes.app?

[xcodes](https://github.com/RobotsAndPencils/xcodes) has been well-received within and outside of Robots and Pencils as an easy way to manage Xcode versions. A command line tool can have a familiar interface for developers, and is also easier to automate than most GUI apps.

Not everyone wants to use a command line tool though, and there's an opportunity to create an even better developer experience with an app. This is also an opportunity for contributors to get more familiar with SwiftUI and Combine on macOS. 

## Code Organization

To begin, we will intentionally not attempt to share code between xcodes and Xcodes.app. In the future, once we have a better idea of the two tools' functionality, we can revisit this decision. An example of code that could be shared are the two AppleAPI libraries which will likely be very similar.

While the intent of xcodes' XcodesKit library was to potentially reuse it in a GUI context, it still makes a lot of assumptions about how the UI works that would prevent that happening immediately. As we reuse that code (by copying and pasting) and tweak it to work in Xcodes.app, we may end up with something that can work in both contexts. 

## Asynchrony

Xcodes.app uses Combine to model asynchronous work. This is different than xcodes, which uses PromiseKit because it began prior to Combine's existence. This means that there is a migration of the existing code that has to happen, but the result is easier to use with a SwiftUI app.

## Dependency Injection

xcodes used Point Free's Environment type, and I'm happy with how that turned out. It looks a lot simpler to implement and grow with a codebase, but still allows setting up test double for tests.

- https://www.pointfree.co/episodes/ep16-dependency-injection-made-easy
- https://www.pointfree.co/episodes/ep18-dependency-injection-made-comfortable
- https://vimeo.com/291588126

## State Management

While I'm curious and eager to try Point Free's [Composable Architecture](https://github.com/pointfreeco/swift-composable-architecture), I'm going to avoid it at first in favour of a simpler AppState ObservableObject. My motivation for this is to try to have something more familiar to a contributor that was also new to SwiftUI, so that the codebase doesn't have too many new or unfamiliar things. If we run into performance or correctness issues in the future I think TCA should be a candidate to reconsider.

## Privilege Escalation

Unlike [xcodes](https://github.com/RobotsAndPencils/xcodes/blob/master/DECISIONS.md#privilege-escalation), there is a better option than running sudo in a Process when we need to escalate privileges in Xcodes.app, namely a privileged helper.

A separate, bundle executable is installed as a privileged helper using SMJobBless and communicates with the main app (the client) over XPC. This helper performs the post-install and xcode-select tasks that would require sudo from the command line. The helper and main app validate each other's bundle ID, version and code signing certificate chain. Validation of the connection is done using the private audit token API. An alternative is to validate the code signature of the client based on the PID from a first "handshake" message. DTS [seems to say](https://developer.apple.com/forums/thread/72881#420409022) that this would also be safe against an attacker PID-wrapping. Because the SMJobBless + XPC examples I found online all use the audit token instead, I decided to go with that. The tradeoff is that this is private API.

Uninstallation is not provided yet. I had this partially implemented (one attempt was based on [DoNotDisturb's approach](https://github.com/objective-see/DoNotDisturb/blob/237b19800fa356f830d1c02715a9a75be08b8924/configure/Helper/HelperInterface.m#L123)) but an issue that I kept hitting was that despite the helper not being installed or running I was able to get a remote object proxy over the connection. Adding a timeout to getVersion might be sufficient as a workaround, as it should return the string immediately.

- [Apple Developer: Creating XPC Services](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html)
- [Objective Development: The Story Behind CVE-2019-13013](https://blog.obdev.at/what-we-have-learned-from-a-vulnerability/)
- [Apple Developer Forums: How to and When to uninstall a privileged helper](https://developer.apple.com/forums/thread/66821)
- [Apple Developer Forums: XPC restricted to processes with the same code signing?](https://developer.apple.com/forums/thread/72881#419817)
- [Wojciech Reguła: Learn XPC exploitation - Part 1: Broken cryptography](https://wojciechregula.blog/post/learn-xpc-exploitation-part-1-broken-cryptography/)
- [Wojciech Reguła: Learn XPC exploitation - Part 2: Say no to the PID!](https://wojciechregula.blog/post/learn-xpc-exploitation-part-2-say-no-to-the-pid/)
- [Wojciech Reguła: Learn XPC exploitation - Part 3: Code injections](https://wojciechregula.blog/post/learn-xpc-exploitation-part-3-code-injections/)
- [Apple Developer: EvenBetterAuthorizationSample](https://developer.apple.com/library/archive/samplecode/EvenBetterAuthorizationSample/Introduction/Intro.html)
- [erikberglund/SwiftPrivilegedHelper](https://github.com/erikberglund/SwiftPrivilegedHelper)
- [aronskaya/smjobbless](https://github.com/aronskaya/smjobbless)
- [securing/SimpleXPCApp](https://github.com/securing/SimpleXPCApp)

## Selecting the active version of Xcode

This isn't a technical decision, but we spent enough time talking about this that it's probably worth sharing. When a user has more than one version of Xcode installed, a specific version of the developer tools can be selected with the `xcode-select` tool. The selected version of tools like xcodebuild or xcrun will be used unless the DEVELOPER_DIR environment variable has been set to a different path. You can read more about this in the `xcode-select` man pages. Notably, the man pages and [some notarization documentation](https://developer.apple.com/documentation/xcode/notarizing_macos_software_before_distribution) use the term "active" to indicate the Xcode version that's been selected. [This](https://developer.apple.com/library/archive/technotes/tn2339/_index.html#//apple_ref/doc/uid/DTS40014588-CH1-HOW_DO_I_SELECT_THE_DEFAULT_VERSION_OF_XCODE_TO_USE_FOR_MY_COMMAND_LINE_TOOLS_) older tech note uses the term "default". And of course, the `xcode-select` tool has the term "select" in its name. xcodes used the terms "select" and "selected" for this functionality, intending to match the xcode-select tool.

Here are the descriptions of these terms from [Apple's Style Guide](https://books.apple.com/ca/book/apple-style-guide/id1161855204):

> active: Use to refer to the app or window currently being used. Preferred to in front.  
> default: OK to use to describe the state of settings before the user changes them. See also preset.  
> preset: Use to refer to a group of customized settings an app provides or the user saves for reuse.  
> select: Use select, not choose, to refer to the action users perform when they select among multiple objects.  

Xcodes.app has this same functionality as xcodes, which still uses `xcode-select` under the hood, but because the main UI is a list of selectable rows, there _may_ be some ambiguity about the meaning of "selected". "Default" has a less clear connection to `xcode-select`'s name, but does accurately describe the behaviour that results. In Xcode 11 Launch Services also uses the selected Xcode version when opening a (GUI) developer tool bundled with Xcode, like Instruments. We could also try to follow Apple's lead by using the term "active" from the `xcode-select` man pages and notarization documentation. According to the style guide "active" already has a clear meaning in a GUI context.

Ultimately, we've decided to align with Apple's usage of "active" and "make active" in this specific context, despite possible confusion with the definition in the style guide. 

## Software Updates

We're familiar with using GitHub releases to distribute pre-built, code signed and notarized versions of `xcodes` via direct download and Homebrew. Ideally we could use GitHub releases here too with an update mechanism more suitable for an app bundle. For distribution outside the Mac App Store, the most popular choice for updates is [Sparkle](https://sparkle-project.org). The v2 branch has been in beta for a long time, but since Xcodes.app isn't (currently) sandboxed, we can use the production-ready v1 releases.

Based on [this blog post](https://yiqiu.me/2015/11/19/sparkle-update-on-github/), we can use GitHub Pages to generate the appcast for Sparkle to point at releases in our repo. We've made a few changes, like putting the source for the Jekyll site on the main branch, and including the EdDSA signature in the appcast. Generating the appcast file manually would be more straightforward, but we can always edit the files on the gh_pages branch manually if we need to, and it's one less step for a release manager to perform when they're already creating the release in the repo.

We're deliberately not capturing system profile data with Sparkle right now, because we don't want it and because it would require additional infrastructure.

We also considered https://github.com/mxcl/AppUpdater, but decided against it because it seemed less battle-tested than Sparkle and currently lacks an open source license.


================================================
FILE: HelperXPCShared/HelperXPCShared.swift
================================================
import Foundation

let machServiceName = "com.xcodesorg.xcodesapp.Helper"
let clientBundleID = "com.xcodesorg.xcodesapp"
let subjectOrganizationalUnit = Bundle.main.infoDictionary!["CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT"] as! String

@objc(HelperXPCProtocol)
protocol HelperXPCProtocol {
    func getVersion(completion: @escaping (String) -> Void)
    func xcodeSelect(absolutePath: String, completion: @escaping (Error?) -> Void)
    func devToolsSecurityEnable(completion: @escaping (Error?) -> Void)
    func addStaffToDevelopersGroup(completion: @escaping (Error?) -> Void)
    func acceptXcodeLicense(absoluteXcodePath: String, completion: @escaping (Error?) -> Void)
    func runFirstLaunch(absoluteXcodePath: String, completion: @escaping (Error?) -> Void)
}


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2019-2021 Robots and Pencils

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<h1><img src="icon.png" align="center" width=50 height=50 /> <img src="IconDark.png" align="center" width=50 height=50 /> <img src="IconMono.png" align="center" width=50 height=50 /> Xcodes.app</h1>

The easiest way to install and switch between multiple versions of Xcode.

_If you're looking for a command-line version of Xcodes.app, try [`xcodes`](https://github.com/XcodesOrg/xcodes)._

![CI](https://github.com/XcodesOrg/XcodesApp/workflows/CI/badge.svg)

![](screenshot_light.png#gh-light-mode-only)
![](screenshot_dark.png#gh-dark-mode-only)

### :tada: Announcement

XcodesApp is now part of the `XcodesOrg` - [read more here](nextstep.md)

## Features

- List all available Xcode versions from [Xcode Releases'](https://xcodereleases.com) data or the Apple Developer website.
- Install any Xcode version, **fully automated** from start to finish. Xcodes uses [`aria2`](https://aria2.github.io), which uses up to 16 connections to download 3-5x faster than URLSession.
- Automatically resumes installs if network errors.
- Apple ID required to download Xcode versions.
- Just click a button to make a version active with `xcode-select`.
- View release notes, OS compatibility, included SDKs and compilers from [Xcode Releases](https://xcodereleases.com).
- Dark/Light Mode supported
- Security Key Authentication supported 
- Support installing Platforms/Runtimes
- Support installing Apple Silicon variants

## Platforms/Runtimes

- Xcodes supports downloading the Apple runtimes via the app. Simply click on the Platform, and Xcodes will install automatically for you.

**Note: iOS 18+, tvOS 18+, watchOS 11+, visionOS 2+ requires that Xcode 16.1 Beta 3+ be installed and active.** 

## Apple Silicon Variants

As of Xcode 26, Apple provides Apple Silicon as well as Universal variants for Xcode versions as well as each runtime. Simply tap on which variant you want installed. To install the Apple Silicon runtime variant Xcode 26 is required to be active.

## Experiments

- Thanks to the wonderful work of [https://github.com/saagarjha/unxip](https://github.com/saagarjha/unxip), turn on the experiment to increase your unxipping time by up to 70%! More can be found on his repo, but bugs, high memory may occur if used.

![](experiment_light.png#gh-light-mode-only)
![](experiment_dark.png#gh-dark-mode-only)

## Localization

Xcodes supports localization in several languages.

The following languages are supported because of the following community users!

|||||
|-|-|-|-|
|French 🇫🇷 |[@dompepin](https://github.com/dompepin)|Italian 🇮🇹 |[gualtierofrigerio](https://github.com/gualtierofrigerio)|
|Spanish 🇪🇸🇲 |[@cesartru88](https://github.com/cesartru88)|Korean 🇰🇷 |[@ryan-son](https://github.com/ryan-son)|
|Russian 🇷🇺 |[@alexmazlov](https://github.com/alexmazlov)|Turkish 🇹🇷 |[@egesucu](https://github.com/egesucu)|
|Hindi 🇮🇳 |[@KGurpreet](https://github.com/KGurpreet)|Chinese-Simplified 🇨🇳|[@megabitsenmzq](https://github.com/megabitsenmzq)|
|Finnish 🇫🇮 |[@marcusziade](https://github.com/marcusziade)|Chinese-Traditional 🇹🇼|[@itszero](https://github.com/itszero)|
|Ukranian 🇺🇦 |[@gelosi](https://github.com/gelosi)|Japanese 🇯🇵|[@tatsuz0u](https://github.com/tatsuz0u)|
|German 🇩🇪|[@drct](https://github.com/drct)|Dutch 🇳🇱|[@jfversluis](https://github/com/jfversluis)|
|Brazilian Portuguese 🇧🇷|[@brunomunizaf](https://github.com/brunomunizaf)|Polish 🇵🇱|[@jakex7](https://github.com/jakex7)|
|Catalan|[@ferranabello](https://github.com/ferranabello)|Greek 🇬🇷|[@alladinian](https://github.com/alladinian)
|Thai 🇹🇭|[@neetrath](https://github.com/neetrath)|

Want to add more languages? Simply create a PR with the updated strings file.

## Installation
v1.X - requires macOS 11 or newer
v2.X - requires macOS 13
v3.X - requires macOS 13 - architecture variants and updated icon.

### Install with Homebrew

Developer ID-signed and notarized release builds are available on Homebrew. These don't require Xcode to already be installed in order to use.

```sh
brew install --cask xcodes
```

### Manually install

1. Download the latest version [here](https://github.com/XcodesOrg/XcodesApp/releases/latest) using the **Xcodes.zip** asset. These are Developer ID-signed and notarized release builds and don't require Xcode to already be installed in order to use.
2. Move the unzipped `Xcodes.app` to your `/Applications` directory

## Support

Xcodes.app and CLI is updated, maintained with contributors like yourself. Even open source libraries and tools come with expenses. If you would like to support Xcodes or donate to the development and maintenance of the tool, it would be greatly appreciated. There is absolutely no obligation!

<a href="https://opencollective.com/xcodesapp" target="_blank">
				<img src="https://opencollective.com/xcodesapp/donate/button@2x.png?color=blue" class="buymeacoffee" width=200 />
</a>

## Development

You'll need macOS 15.6 Ventura and Xcode 26 in order to build and run Xcodes.app.

`Unxip` and `aria2` must be compiled as a universal binary
```
# compile for Intel
 swiftc -parse-as-library -O -target x86_64-apple-macos11 unxip.swift
# compile for M1
 swiftc -parse-as-library -O -target arm64-apple-macos11 unxip.swift

# combine for universal binary
 lipo -create -output unxip unxip_intel unxip_m1  
# check it
 lipo -archs unxip
```


[`xcode-install`](https://github.com/xcpretty/xcode-install) and [fastlane/spaceship](https://github.com/fastlane/fastlane/tree/master/spaceship) both deserve credit for figuring out the hard parts of what makes this possible.


<details>
<summary>Releasing a new version</summary>

Follow the steps below to build and release a new version of Xcodes.app. For any of the git steps, you can use your preferred tool, but please sign the tag.

```sh
# Update the version number in Xcode and commit the change, if necessary

# Question: Did anything in XPCHelper change?
# - com.xcodesorg.xcodesapp.Helper folder and HelperXPCShared
# - if so, bump the version number in com.xcodesorg.xcodesapp.Helper target.
# Note: you do not have to bump the version number if nothing has changed.
# Note2: If you do bump the version, the end user, must re-install the XPCHelper and give permission again.

# Increment the build number
scripts/increment_build_number.sh

# Commit the change
git add Xcodes/Resources/Info.plist
git commit -asm "Increment build number"

# Tag the latest commit
# Replace $VERSION and $BUILD below with the latest real values
git tag -asm "v$VERSIONb$BUILD" "v$VERSIONb$BUILD"

# Push to origin
git push --follow-tags

# Build the app
# Make sure you have the Xcode Selected you want to build with
scripts/package_release.sh

# Notarize the app
# Do this from the Product directory so the app is zipped without being nested inside Product
# Create a app specific password on appleid.apple.com if you haven't already
# xcrun notarytool store-credentials "AC_PASSWORD" \
#              --apple-id "test@example.com" \
#              --team-id "teamid" \
#               --password "app specific password"

pushd Product
../scripts/notarize.sh Xcodes.zip <MYORG>

# Sign the .zip for Sparkle, note the signature in the output for later
# If you're warned about the signing key not being found, see the Xcodes 1Password vault for the key and installation instructions.
../scripts/sign_update Xcodes.zip
popd

# Go to https://github.com/XcodesOrg/XcodesApp/releases
# If there are uncategorized PRs, add the appropriate label and run the Release Drafter action manually
# Edit the latest draft release
# Set its tag to the tag you just pushed
# Set its title to a string with the format "$VERSION ($BUILD)"
# Polish the draft release notes, if necessary
# Add the signature to the bottom of the release notes in a comment, like:
<!-- sparkle:edSignature=$SIGNATURE -->
# Attach the zip that was created in the Product directory to the release
# Publish the release

shasum -a 256 xcodes.zip
# Update the [Homebrew Cask](https://github.com/XcodesOrg/homebrew-cask/blob/master/Casks/x/xcodes.rb).
```
</details>

## Maintainers

[Matt Kiazyk](https://github.com/mattkiazyk) - [Twitter](https://www.twitter.com/mattkiazyk)

[Twitter](https://twitter.com/xcodesApp) | [GitHub](https://github.com/xcodesOrg) | [Mastadon](https://iosdev.space/@XcodesApp) |


================================================
FILE: Scripts/export_options.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>method</key>
	<string>developer-id</string>
</dict>
</plist>


================================================
FILE: Scripts/fix_libfido2_framework.sh
================================================
#!/bin/sh

# Fix libfido2.framework structure for macOS validation
# If this script is not run, the build will fail because xcodebuild is expecting the library in a specific structure
FRAMEWORK_PATH="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Contents/Frameworks/libfido2.framework"

if [ -d "$FRAMEWORK_PATH" ] && [ -f "$FRAMEWORK_PATH/Info.plist" ] && [ ! -d "$FRAMEWORK_PATH/Versions" ]; then
    echo "Fixing libfido2.framework bundle structure..."

    # Create proper bundle structure
    mkdir -p "$FRAMEWORK_PATH/Versions/A/Resources"

    # Move files to proper locations
    mv "$FRAMEWORK_PATH/Info.plist" "$FRAMEWORK_PATH/Versions/A/Resources/"
    mv "$FRAMEWORK_PATH/libfido2" "$FRAMEWORK_PATH/Versions/A/"
    if [ -f "$FRAMEWORK_PATH/LICENSE" ]; then
        mv "$FRAMEWORK_PATH/LICENSE" "$FRAMEWORK_PATH/Versions/A/"
    fi

    # Create symbolic links
    ln -sf A "$FRAMEWORK_PATH/Versions/Current"
    ln -sf Versions/Current/libfido2 "$FRAMEWORK_PATH/libfido2"
    ln -sf Versions/Current/Resources "$FRAMEWORK_PATH/Resources"

    echo "libfido2.framework structure fixed"
fi


================================================
FILE: Scripts/increment_build_number.sh
================================================
#!/bin/sh
#
# Increment build number
#
# This will get the latest build number from git tags, add 1, then set it in the Info.plist.
# Assumes that build numbers are monotonically increasing positive integers, across version numbers.
# Tags must be named v$version_numberb$build_number, e.g. v1.2.3b456

infoplist_file="$(pwd)/Xcodes/Resources/Info.plist"

# Get latest tag hash matching pattern
hash=$(git rev-list --tags="v*" --max-count=1)
# Get latest tag at hash that matches the same pattern as a prefix in order to support commits with multiple tags
last_tag=$(git describe --tags --match "v*" "$hash")
# Get build number from last component of tag name
last_build_number=$(echo "$last_tag" | grep -o "b.*" | cut -c 2-)

build_number=$(($last_build_number + 1))

/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $build_number" "${infoplist_file}"


================================================
FILE: Scripts/notarize.sh
================================================
#!/bin/sh
#
# Notarize
#
# Uploads to Apple's notarization service, polls until it completes, staples the ticket to the built app, then creates a new zip.
#
# Requires four arguments:
#   - Apple ID username
#   - Apple ID app-specific password (store this in your Keychain and use the @keychain:$NAME syntax to prevent your password from being added to your shell history)
#   - App Store Connect provider name
#   - Path to .app to upload
#
# Assumes that there's a .app beside the .zip with the same name so it can be stapled and re-zipped.
#
# E.g. notarize.sh "test@example.com" "@keychain:altool" MyOrg Xcodes.zip
#
# https://developer.apple.com/documentation/xcode/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow
# Adapted from https://github.com/keybase/client/blob/46f5df0aa64ff19198ba7b044bbb7cd907c0be9f/packaging/desktop/package_darwin.sh

file="$1"
team_id="$2"

echo "Uploading to notarization service"

result=$(xcrun notarytool submit "$file" \
    --keychain-profile "AC_PASSWORD" \
    --team-id "$team_id" \
    --wait) 
# echo "done1"
echo $result

# My grep/awk is bad and I can't figure out how to get the UUID out properly
# uuid=$("$result" | \
#     grep 'id:' | tail -n1 | \
#     cut -d":" -f2-)

echo "Successfully uploaded to notarization service, polling for result: $uuid"

# we should check here using the info (or notarytool log) to check the results and log
# 

#     fullstatus=$(xcrun notarytool info "$uuid" \
#         --keychain-profile "AC_PASSWORD" 2>&1)
#     status=$(echo "$fullstatus" | grep 'status\:' | awk '{ print $2 }')
#     if [ "$status" = "Accepted" ]; then
#       echo "Notarization success"
#       exit 0
#     else
#       echo "Notarization failed, full status below"
#       echo "$fullstatus"
#       exit 1
#     fi

# Remove .zip
rm $file

# Staple ticket to .app
app_path="$(basename -s ".zip" "$file").app"
xcrun stapler staple "$app_path"

# Zip the stapled app for distribution
ditto -c -k --sequesterRsrc --keepParent "$app_path" "$file"


================================================
FILE: Scripts/package_release.sh
================================================
#!/bin/bash
#
# Package release
#
# This will build and archive the app and then compress it in a .zip file at Product/Xcodes.zip
# You must already have all required code signing assets installed on your computer

PROJECT_NAME=Xcodes
PROJECT_DIR=$(pwd)/$PROJECT_NAME/Resources
SCRIPTS_DIR=$(pwd)/Scripts
INFOPLIST_FILE="Info.plist"

# If needed ensure that the unxip binary is signed with a hardened runtime so we can notarize
# codesign --force --options runtime --sign "Developer ID Application: Robots and Pencils Inc." $PROJECT_DIR/unxip 

# Ensure a clean build
rm -rf Archive/*
rm -rf Product/*
xcodebuild clean -project $PROJECT_NAME.xcodeproj -configuration Release -alltargets

# Archive the app and export for release distribution
xcodebuild archive -project $PROJECT_NAME.xcodeproj -scheme $PROJECT_NAME -archivePath Archive/$PROJECT_NAME.xcarchive
xcodebuild -archivePath Archive/$PROJECT_NAME.xcarchive -exportArchive -exportPath Product/$PROJECT_NAME -exportOptionsPlist "${SCRIPTS_DIR}/export_options.plist"
cp -a "Product/$PROJECT_NAME/$PROJECT_NAME.app" "Product/$PROJECT_NAME.app"

# Create a ZIP archive suitable for altool.
/usr/bin/ditto -c -k --keepParent "Product/$PROJECT_NAME.app" "Product/$PROJECT_NAME.zip"


================================================
FILE: Scripts/uninstall_privileged_helper.sh
================================================
#!/bin/bash

PRIVILEGED_HELPER_LABEL=com.xcodesorg.xcodesapp.Helper

sudo rm /Library/PrivilegedHelperTools/$PRIVILEGED_HELPER_LABEL
sudo rm /Library/LaunchDaemons/$PRIVILEGED_HELPER_LABEL.plist
sudo launchctl bootout system/$PRIVILEGED_HELPER_LABEL      #'Boot-out failed: 36: Operation now in progress' is OK output

echo "Querying launchd..."
LAUNCHD_OUTPUT=$(sudo launchctl list | grep $PRIVILEGED_HELPER_LABEL)


if [ -z "$LAUNCHD_OUTPUT" ]
then
      echo "Finished successfully."
else
      echo "WARNING: $PRIVILEGED_HELPER_LABEL is not removed"
fi


================================================
FILE: Xcodes/AcknowledgementsGenerator/.gitignore
================================================
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/


================================================
FILE: Xcodes/AcknowledgementsGenerator/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:">
   </FileRef>
</Workspace>


================================================
FILE: Xcodes/AcknowledgementsGenerator/Package.swift
================================================
// swift-tools-version:5.4

import PackageDescription

let package = Package(
    name: "AcknowledgementsGenerator",
    platforms: [.macOS(.v11)],
    products: [
        .executable(
            name: "AcknowledgementsGenerator",
            targets: ["AcknowledgementsGenerator"]
        ),
    ],
    dependencies: [
    ],
    targets: [
        .executableTarget(
            name: "AcknowledgementsGenerator",
            dependencies: []
        ),
    ]
)


================================================
FILE: Xcodes/AcknowledgementsGenerator/README.md
================================================
# AcknowledgementsGenerator

Scans an Xcode project's checked-out SPM packages for license files, then combines them into a single RTF file.

Based on https://github.com/MacPaw/spm-licenses.


================================================
FILE: Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/Extensions/CollectionExtensions.swift
================================================
//
//  CollectionExtensions.swift
//  spm-licenses
//
//  Created by Sergii Kryvoblotskyi on 11/11/19.
//  Copyright © 2019 MacPaw. All rights reserved.
//

import Foundation

public extension Collection {

    /// Returns the element at the specified index iff it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}


================================================
FILE: Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/Extensions/StringExtensions.swift
================================================
//
//  StringExtensions.swift
//  spm-licenses
//
//  Created by Sergii Kryvoblotskyi on 11/11/19.
//  Copyright © 2019 MacPaw. All rights reserved.
//

import Foundation

public extension String {
    
    var nsString: NSString {
        (self as NSString)
    }

    var pathExtension: String {
        return nsString.pathExtension
    }

    var lastPathComponent: String {
        return nsString.lastPathComponent
    }

    var deletingLastPathComponent: String {
        return nsString.deletingLastPathComponent
    }

    var stringByDeletingPathExtension: String {
        return nsString.deletingPathExtension
    }

    var expandingTildeInPath: String {
        return nsString.expandingTildeInPath
    }
    
    func appendingPathComponent(_ component: String) -> String {
        return nsString.appendingPathComponent(component)
    }
}


================================================
FILE: Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/Tools/Xcode.swift
================================================
//
//  Xcode.swift
//  spm-licenses
//
//  Created by Sergii Kryvoblotskyi on 11/11/19.
//  Copyright © 2019 MacPaw. All rights reserved.
//

import Foundation

struct Xcode {
    
    static var derivedDataURL: URL {
        if let overridenPath = readOverridenDerivedDataPath() {
            return URL(fileURLWithPath: overridenPath.expandingTildeInPath)
        }
        let defaultPath = "~/Library/Developer/Xcode/DerivedData/".expandingTildeInPath
        return URL(fileURLWithPath: defaultPath)
    }
}

//defaults read com.apple.dt.Xcode.plist IDECustomDerivedDataLocation
//If the line returns
//
//The domain/default pair of (com.apple.dt.Xcode.plist, IDECustomDerivedDataLocation) does not exist
//it's the default path ~/Library/Developer/Xcode/DerivedData/ otherwise the custom path.
private extension Xcode {
    
    static func readOverridenDerivedDataPath() -> String? {
        let task = Process()
        let pipe = Pipe()
        task.executableURL = URL(fileURLWithPath: "/usr/bin/defaults")
        task.arguments = ["read","com.apple.dt.Xcode.plist", "IDECustomDerivedDataLocation"]
        task.standardOutput = pipe
        try? task.run()
        let handle = pipe.fileHandleForReading
        let data = handle.readDataToEndOfFile()
        let path = String(data: data, encoding: String.Encoding.utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
        return (path?.isEmpty ?? true) ? nil : path
    }
}

extension Xcode {
    
    struct Project {
        let url: URL
        let info: [String: Any]
        var workspacePath: String? {
            return info["WorkspacePath"] as? String
        }
    }
}

extension Xcode.Project {
    
    struct License {
        
        let url: URL
        let name: String
    }
}

extension Xcode.Project.License {
    
    func makeRepresentation() throws -> [String: String] {
        let data = try Data(contentsOf: url)
        let text = String(data: data, encoding: .utf8) ?? ""
        return [
            "Title": name,
            "Type": "PSGroupSpecifier",
            "FooterText": text
        ]
    }
}


================================================
FILE: Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/main.swift
================================================
//
//  main.swift
//  spm-licenses
//
//  Created by Sergii Kryvoblotskyi on 11/11/19.
//  Copyright © 2019 MacPaw. All rights reserved.
//

import Foundation
import AppKit

let arguments = CommandLine.arguments
guard let projectIndex = arguments.firstIndex(of: "-p"), let projectPath = arguments[safe: projectIndex + 1] else {
    print("Project path is missing. Specify -p.")
    exit(EXIT_FAILURE)
}

guard let outputIndex = arguments.firstIndex(of: "-o"), let outputPath = arguments[safe: outputIndex + 1] else {
    print("Output path is missing. Specify -o.")
    exit(EXIT_FAILURE)
}

let fileManager = FileManager.default

let projectURL = URL(fileURLWithPath: projectPath.expandingTildeInPath)
if !fileManager.fileExists(atPath: projectURL.path) {
    print("xcodeproj not found at \(projectURL)")
    exit(EXIT_FAILURE)
}

let packageURL = projectURL.appendingPathComponent("project.xcworkspace/xcshareddata/swiftpm/Package.resolved")
if !fileManager.fileExists(atPath: packageURL.path) {
    print("Package.resolved not found at \(packageURL)")
    exit(EXIT_FAILURE)
}

let packageData = try Data(contentsOf: packageURL)
let packageInfo = try JSONSerialization.jsonObject(with: packageData, options: .allowFragments)

guard let package = packageInfo as? [String: Any] else {
    print("Invalid package format")
    exit(EXIT_FAILURE)
}

guard let object = package["object"] as? [String: Any] else {
    print("Invalid obejct format")
    exit(EXIT_FAILURE)
}

guard let pins = object["pins"] as? [[String: Any]] else {
    print("Invalid pins format")
    exit(EXIT_FAILURE)
}

let projectsURL = Xcode.derivedDataURL
func projectsInfo(at url: URL) throws -> [Xcode.Project] {
    try fileManager
        .contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
        .map { $0.appendingPathComponent("info.plist") }
        .compactMap {
            guard let info = NSDictionary(contentsOf: $0) as? [String: Any] else { return nil }
            return Xcode.Project(url: $0, info: info)
    }
}
let projects = try projectsInfo(at: projectsURL)

// Despite the naming, if the project only has an xcodeproj and not an xcworkspace, the WorkspacePath value will be the path to the xcodeproj
guard let currentProject = projects.first(where: ({ $0.workspacePath == projectPath.expandingTildeInPath })) else {
    print("Derived data missing for project")
    exit(EXIT_FAILURE)
}

let checkouts = currentProject.url.deletingLastPathComponent().appendingPathComponent("SourcePackages/checkouts")
let checkedDependencies = try fileManager.contentsOfDirectory(at: checkouts, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)

let spmLicences: [Xcode.Project.License] = checkedDependencies.compactMap {
    let supportedFilenames = ["LICENSE", "LICENSE.txt", "LICENSE.md"]
    for filename in supportedFilenames {
        let licenseURL = $0.appendingPathComponent(filename)
        if fileManager.fileExists(atPath: licenseURL.path) {
            return Xcode.Project.License(url: licenseURL, name: $0.lastPathComponent)
        }
    }
    return nil
}

var manualLicenses: [Xcode.Project.License] = []
let enumerator = fileManager.enumerator(at: projectURL.deletingLastPathComponent(), includingPropertiesForKeys: [URLResourceKey.nameKey], options: .skipsHiddenFiles)!
for case let url as URL in enumerator where url.lastPathComponent.hasSuffix(".LICENSE") {
    manualLicenses.append(
        Xcode.Project.License(
            url: url, 
            name: url.lastPathComponent.replacingOccurrences(of: ".LICENSE", with: "")
        )
    )
}

let licences = spmLicences + manualLicenses

let acknowledgementsAttributedString = NSMutableAttributedString()
for licence in licences {
    acknowledgementsAttributedString.append(NSAttributedString(string: licence.name + "\n\n", attributes: [.font: NSFont.preferredFont(forTextStyle: .title2)]))
    let licenseContents = try String(contentsOf: licence.url)
    acknowledgementsAttributedString.append(NSAttributedString(string: licenseContents + "\n\n", attributes: [.font: NSFont.preferredFont(forTextStyle: .body)]))
}

guard let data = acknowledgementsAttributedString.rtf(from: NSRange(location: 0, length: acknowledgementsAttributedString.length), documentAttributes: [:]) else {
    print("Failed to create RTF data")
    exit(EXIT_FAILURE)
}
try data.write(to: URL(fileURLWithPath: outputPath.expandingTildeInPath))

print("Licenses have been saved to \(outputPath)")


================================================
FILE: Xcodes/AcknowledgementsGenerator/spm-licenses.LICENSE
================================================
MIT License

Copyright (c) 2019 MacPaw

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Xcodes/AppleAPI/.gitignore
================================================
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/


================================================
FILE: Xcodes/AppleAPI/Package.swift
================================================
// swift-tools-version:5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "AppleAPI",
    platforms: [.macOS(.v11)],
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(
            name: "AppleAPI",
            targets: ["AppleAPI"]),
    ],
    dependencies: [
        .package(url: "https://github.com/xcodesOrg/swift-srp", branch: "main")
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .target(
            name: "AppleAPI",
            dependencies: [.product(name: "SRP", package: "swift-srp")]),
        .testTarget(
            name: "AppleAPITests",
            dependencies: ["AppleAPI"]),
    ]
)


================================================
FILE: Xcodes/AppleAPI/README.md
================================================
# AppleAPI

A description of this package.


================================================
FILE: Xcodes/AppleAPI/Sources/AppleAPI/Client.swift
================================================
import Foundation
import Combine
import SRP
import Crypto
import CommonCrypto


public class Client {
    private static let authTypes = ["sa", "hsa", "non-sa", "hsa2"]

    public init() {}

    // MARK: - Login

    public func srpLogin(accountName: String, password: String) -> AnyPublisher<AuthenticationState, Swift.Error> {
        var serviceKey: String!
        
        let client = SRPClient(configuration: SRPConfiguration<SHA256>(.N2048))
        let clientKeys = client.generateKeys()
        let a = clientKeys.public

        return Current.network.dataTask(with: URLRequest.itcServiceKey)
            .map(\.data)
            .decode(type: ServiceKeyResponse.self, decoder: JSONDecoder())
            .flatMap { serviceKeyResponse -> AnyPublisher<(String, String), Swift.Error> in
                serviceKey = serviceKeyResponse.authServiceKey
                
                // Fixes issue https://github.com/RobotsAndPencils/XcodesApp/issues/360
                // On 2023-02-23, Apple added a custom implementation of hashcash to their auth flow
                // Without this addition, Apple ID's would get set to locked
                return self.loadHashcash(accountName: accountName, serviceKey: serviceKey)
                    .map { return (serviceKey, $0)}
                    .eraseToAnyPublisher()
            }
            .flatMap { (serviceKey, hashcash) -> AnyPublisher<(String, String, ServerSRPInitResponse), Swift.Error> in
                
                return Current.network.dataTask(with: URLRequest.SRPInit(serviceKey: serviceKey, a: Data(a.bytes).base64EncodedString(), accountName: accountName))
                    .map(\.data)
                    .decode(type: ServerSRPInitResponse.self, decoder: JSONDecoder())
                    .map { return (serviceKey, hashcash, $0) }
                    .eraseToAnyPublisher()
            }
            .flatMap { (serviceKey, hashcash, srpInit) -> AnyPublisher<URLSession.DataTaskPublisher.Output, Swift.Error> in
                guard let decodedB = Data(base64Encoded: srpInit.b) else {
                    return Fail(error: AuthenticationError.srpInvalidPublicKey)
                        .eraseToAnyPublisher()
                }
                
                guard let decodedSalt = Data(base64Encoded: srpInit.salt) else {
                    return Fail(error: AuthenticationError.srpInvalidPublicKey)
                        .eraseToAnyPublisher()
                }

                let iterations = srpInit.iteration
                
                do {
                    guard let encryptedPassword = self.pbkdf2(password: password, saltData: decodedSalt, keyByteCount: 32, prf: CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), rounds: iterations, protocol: srpInit.protocol) else {
                        return Fail(error: AuthenticationError.srpInvalidPublicKey)
                            .eraseToAnyPublisher()
                    }
                    
                    let sharedSecret = try client.calculateSharedSecret(password: encryptedPassword, salt: [UInt8](decodedSalt), clientKeys: clientKeys, serverPublicKey: .init([UInt8](decodedB)))
                    
                    let m1 = client.calculateClientProof(username: accountName, salt: [UInt8](decodedSalt), clientPublicKey: a, serverPublicKey: .init([UInt8](decodedB)), sharedSecret: .init(sharedSecret.bytes))
                    let m2 = client.calculateServerProof(clientPublicKey: a, clientProof: m1, sharedSecret: .init([UInt8](sharedSecret.bytes)))

                    return Current.network.dataTask(with: URLRequest.SRPComplete(serviceKey: serviceKey, hashcash: hashcash, accountName: accountName, c: srpInit.c, m1: Data(m1).base64EncodedString(), m2: Data(m2).base64EncodedString()))
                            .mapError { $0 as Swift.Error }
                            .eraseToAnyPublisher()
                } catch {
                    return Fail(error: AuthenticationError.srpInvalidPublicKey)
                        .eraseToAnyPublisher()
                }
            }
            .flatMap { result -> AnyPublisher<AuthenticationState, Swift.Error> in
                let (data, response) = result
                return Just(data)
                    .decode(type: SignInResponse.self, decoder: JSONDecoder())
                    .flatMap { responseBody -> AnyPublisher<AuthenticationState, Swift.Error> in
                        let httpResponse = response as! HTTPURLResponse
                        
                        switch httpResponse.statusCode {
                        case 200:
                            return Current.network.dataTask(with: URLRequest.olympusSession)
                                .map { _ in AuthenticationState.authenticated }
                                .mapError { $0 as Swift.Error }
                                .eraseToAnyPublisher()
                        case 401:
                            return Fail(error: AuthenticationError.invalidUsernameOrPassword(username: accountName))
                                .eraseToAnyPublisher()
                        case 403:
                            let errorMessage = responseBody.serviceErrors?.first?.description.replacingOccurrences(of: "-20209: ", with: "") ?? ""
                            return Fail(error: AuthenticationError.accountLocked(errorMessage))
                                .eraseToAnyPublisher()
                        case 409:
                            return self.handleTwoStepOrFactor(data: data, response: response, serviceKey: serviceKey)
                        case 412 where Client.authTypes.contains(responseBody.authType ?? ""):
                            return Fail(error: AuthenticationError.appleIDAndPrivacyAcknowledgementRequired)
                                .eraseToAnyPublisher()
                        default:
                            return Fail(error: AuthenticationError.unexpectedSignInResponse(statusCode: httpResponse.statusCode,
                                                                 message: responseBody.serviceErrors?.map { $0.description }.joined(separator: ", ")))
                                .eraseToAnyPublisher()
                        }
                    }
                    .eraseToAnyPublisher()
            }
            .mapError { $0 as Swift.Error }
            .eraseToAnyPublisher()
    }
    
    func loadHashcash(accountName: String, serviceKey: String) -> AnyPublisher<String, Swift.Error> {
        
        Result {
            try URLRequest.federate(account: accountName, serviceKey: serviceKey)
        }
        .publisher
        .flatMap { request in
            Current.network.dataTask(with: request)
                .mapError { $0 as Error }
                .tryMap { (data, response) throws -> (String) in
                    guard let urlResponse = response as? HTTPURLResponse else {
                        throw AuthenticationError.invalidSession
                    }
                    switch urlResponse.statusCode {
                    case 200..<300:
                        
                        let httpResponse = response as! HTTPURLResponse
                        guard let bitsString = httpResponse.allHeaderFields["X-Apple-HC-Bits"] as? String, let bits = UInt(bitsString) else {
                            throw AuthenticationError.invalidHashcash
                        }
                        guard let challenge = httpResponse.allHeaderFields["X-Apple-HC-Challenge"] as? String else {
                            throw AuthenticationError.invalidHashcash
                        }
                        guard let hashcash = Hashcash().mint(resource: challenge, bits: bits) else {
                            throw AuthenticationError.invalidHashcash
                        }
                        return (hashcash)
                    case 400, 401:
                        throw AuthenticationError.invalidHashcash
                    case let code:
                        throw AuthenticationError.badStatusCode(statusCode: code, data: data, response: urlResponse)
                    }
                }
        }
        .eraseToAnyPublisher()
    
    }

    func handleTwoStepOrFactor(data: Data, response: URLResponse, serviceKey: String) -> AnyPublisher<AuthenticationState, Swift.Error> {
        let httpResponse = response as! HTTPURLResponse
        let sessionID = (httpResponse.allHeaderFields["X-Apple-ID-Session-Id"] as! String)
        let scnt = (httpResponse.allHeaderFields["scnt"] as! String)

        return Current.network.dataTask(with: URLRequest.authOptions(serviceKey: serviceKey, sessionID: sessionID, scnt: scnt))
            .map(\.data)
            .decode(type: AuthOptionsResponse.self, decoder: JSONDecoder())
            .flatMap { authOptions -> AnyPublisher<AuthenticationState, Error> in
                switch authOptions.kind {
                case .twoStep:
                    return Fail(error: AuthenticationError.accountUsesTwoStepAuthentication)
                        .eraseToAnyPublisher()
                case .twoFactor, .securityKey:
                    return self.handleTwoFactor(serviceKey: serviceKey, sessionID: sessionID, scnt: scnt, authOptions: authOptions)
                        .eraseToAnyPublisher()
                case .unknown:
                    let possibleResponseString = String(data: data, encoding: .utf8)
                    return Fail(error: AuthenticationError.accountUsesUnknownAuthenticationKind(possibleResponseString))
                        .eraseToAnyPublisher()
                }
            }
            .eraseToAnyPublisher()
    }
    
    func handleTwoFactor(serviceKey: String, sessionID: String, scnt: String, authOptions: AuthOptionsResponse) -> AnyPublisher<AuthenticationState, Error> {
        let option: TwoFactorOption

        // SMS was sent automatically 
        if authOptions.smsAutomaticallySent {
            option = .smsSent(authOptions.trustedPhoneNumbers!.first!)
        // SMS wasn't sent automatically because user needs to choose a phone to send to
        } else if authOptions.canFallBackToSMS {
            option = .smsPendingChoice
            // Code is shown on trusted devices
        } else if authOptions.fsaChallenge != nil {
            option = .securityKey
            // User needs to use a physical security key to respond to the challenge
        } else {
            option = .codeSent
        }
        
        let sessionData = AppleSessionData(serviceKey: serviceKey, sessionID: sessionID, scnt: scnt)
        return Just(AuthenticationState.waitingForSecondFactor(option, authOptions, sessionData))
            .setFailureType(to: Error.self)
            .eraseToAnyPublisher()
    }
    
    // MARK: - Continue 2FA
    
    public func requestSMSSecurityCode(to trustedPhoneNumber: AuthOptionsResponse.TrustedPhoneNumber, authOptions: AuthOptionsResponse, sessionData: AppleSessionData) -> AnyPublisher<AuthenticationState, Error> {
        Result {
            try URLRequest.requestSecurityCode(serviceKey: sessionData.serviceKey, sessionID: sessionData.sessionID, scnt: sessionData.scnt, trustedPhoneID: trustedPhoneNumber.id)
        }
        .publisher
        .flatMap { request in
            Current.network.dataTask(with: request)
                .mapError { $0 as Error } 
        }
        .map { _ in AuthenticationState.waitingForSecondFactor(.smsSent(trustedPhoneNumber), authOptions, sessionData) }
        .eraseToAnyPublisher()
    }
    
    public func submitSecurityCode(_ code: SecurityCode, sessionData: AppleSessionData) -> AnyPublisher<AuthenticationState, Error> {
        Result {
            try URLRequest.submitSecurityCode(serviceKey: sessionData.serviceKey, sessionID: sessionData.sessionID, scnt: sessionData.scnt, code: code)
        }
        .publisher
        .flatMap { request in
            Current.network.dataTask(with: request)
                .mapError { $0 as Error }
                .tryMap { (data, response) throws -> (Data, URLResponse) in
                    guard let urlResponse = response as? HTTPURLResponse else { return (data, response) }
                    switch urlResponse.statusCode {
                    case 200..<300:
                        return (data, urlResponse)
                    case 400, 401:
                        throw AuthenticationError.incorrectSecurityCode
                    case 412:
                        throw AuthenticationError.appleIDAndPrivacyAcknowledgementRequired
                    case let code:
                        throw AuthenticationError.badStatusCode(statusCode: code, data: data, response: urlResponse)
                    }
                }
                .flatMap { (data, response) -> AnyPublisher<AuthenticationState, Error> in
                    self.updateSession(serviceKey: sessionData.serviceKey, sessionID: sessionData.sessionID, scnt: sessionData.scnt)
                }
        }
        .eraseToAnyPublisher()
    }
    
    public func submitChallenge(response: Data, sessionData: AppleSessionData) -> AnyPublisher<AuthenticationState, Error> {
        Result {
            URLRequest.respondToChallenge(serviceKey: sessionData.serviceKey, sessionID: sessionData.sessionID, scnt: sessionData.scnt, response: response)
        }
        .publisher
        .flatMap { request in
            Current.network.dataTask(with: request)
                .mapError { $0 as Error }
                .tryMap { (data, response) throws -> (Data, URLResponse) in
                    guard let urlResponse = response as? HTTPURLResponse else { return (data, response) }
                    switch urlResponse.statusCode {
                    case 200..<300:
                        return (data, urlResponse)
                    case 400, 401:
                        throw AuthenticationError.incorrectSecurityCode
                    case 412:
                        throw AuthenticationError.appleIDAndPrivacyAcknowledgementRequired
                    case let code:
                        throw AuthenticationError.badStatusCode(statusCode: code, data: data, response: urlResponse)
                    }
                }
                .flatMap { (data, response) -> AnyPublisher<AuthenticationState, Error> in
                    self.updateSession(serviceKey: sessionData.serviceKey, sessionID: sessionData.sessionID, scnt: sessionData.scnt)
                }
        }.eraseToAnyPublisher()
    }
    
    // MARK: - Session
    
    /// Use the olympus session endpoint to see if the existing session is still valid
    public func validateSession() -> AnyPublisher<Void, Error> {
        return Current.network.dataTask(with: URLRequest.olympusSession)
            .tryMap { result -> Data in
                let httpResponse = result.response as! HTTPURLResponse
                if httpResponse.statusCode == 401 {
                    throw AuthenticationError.notAuthorized
                }

                return result.data
            }
            .decode(type: AppleSession.self, decoder: JSONDecoder())
            .tryMap { session in
                // A user that is a non-paid Apple Developer will have a provider == nil
                // Those users can still download Xcode.
                // Non Apple Developers will get caught in the download as invalid
//                if session.provider == nil {
//                    throw AuthenticationError.notDeveloperAppleId
//                }
            }
            .eraseToAnyPublisher()
    }
    
    func updateSession(serviceKey: String, sessionID: String, scnt: String) -> AnyPublisher<AuthenticationState, Error> {
        return Current.network.dataTask(with: URLRequest.trust(serviceKey: serviceKey, sessionID: sessionID, scnt: scnt))
            .flatMap { (data, response) in
                Current.network.dataTask(with: URLRequest.olympusSession)
                    .map { _ in AuthenticationState.authenticated }
            }
            .mapError { $0 as Error }
            .eraseToAnyPublisher()
    }

    func sha256(data : Data) -> Data {
        var hash = [UInt8](repeating: 0,  count: Int(CC_SHA256_DIGEST_LENGTH))
        data.withUnsafeBytes {
            _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
        }
        return Data(hash)
    }

    private func pbkdf2(password: String, saltData: Data, keyByteCount: Int, prf: CCPseudoRandomAlgorithm, rounds: Int, protocol srpProtocol: SRPProtocol) -> Data? {
        guard let passwordData = password.data(using: .utf8) else { return nil }
        let hashedPasswordDataRaw = sha256(data: passwordData)
        let hashedPasswordData = switch srpProtocol {
        case .s2k: hashedPasswordDataRaw
        // the legacy s2k_fo protocol requires hex-encoding the digest before performing PBKDF2.
        case .s2k_fo: Data(hashedPasswordDataRaw.hexEncodedString().lowercased().utf8)
        }

        var derivedKeyData = Data(repeating: 0, count: keyByteCount)
        let derivedCount = derivedKeyData.count
        let derivationStatus: Int32 = derivedKeyData.withUnsafeMutableBytes { derivedKeyBytes in
            let keyBuffer: UnsafeMutablePointer<UInt8> =
                derivedKeyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
            return saltData.withUnsafeBytes { saltBytes -> Int32 in
                let saltBuffer: UnsafePointer<UInt8> = saltBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
                return hashedPasswordData.withUnsafeBytes { hashedPasswordBytes -> Int32 in
                    let passwordBuffer: UnsafePointer<UInt8> = hashedPasswordBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
                    return CCKeyDerivationPBKDF(
                        CCPBKDFAlgorithm(kCCPBKDF2),
                        passwordBuffer,
                        hashedPasswordData.count,
                        saltBuffer,
                        saltData.count,
                        prf,
                        UInt32(rounds),
                        keyBuffer,
                        derivedCount)
                }
            }
        }
        return derivationStatus == kCCSuccess ? derivedKeyData : nil
    }

}

// MARK: - Types

public enum AuthenticationState: Equatable {
    case unauthenticated
    case waitingForSecondFactor(TwoFactorOption, AuthOptionsResponse, AppleSessionData)
    case authenticated
    case notAppleDeveloper
}

public enum AuthenticationError: Swift.Error, LocalizedError, Equatable {
    case invalidSession
    case invalidHashcash
    case invalidUsernameOrPassword(username: String)
    case incorrectSecurityCode
    case unexpectedSignInResponse(statusCode: Int, message: String?)
    case appleIDAndPrivacyAcknowledgementRequired
    case accountUsesTwoStepAuthentication
    case accountUsesUnknownAuthenticationKind(String?)
    case accountLocked(String)
    case badStatusCode(statusCode: Int, data: Data, response: HTTPURLResponse)
    case notDeveloperAppleId
    case notAuthorized
    case invalidResult(resultString: String?)
    case srpInvalidPublicKey
    
    public var errorDescription: String? {
        switch self {
        case .invalidSession:
            return "Your authentication session is invalid. Try signing in again."
        case .invalidHashcash:
            return "Could not create a hashcash for the session."
        case .invalidUsernameOrPassword:
            return "Invalid username and password combination."
        case .incorrectSecurityCode:
            return "The code that was entered is incorrect."
        case let .unexpectedSignInResponse(statusCode, message):
            return """
                Received an unexpected sign in response. If you continue to have problems, please submit a bug report in the Help menu and include the following information:

                Status code: \(statusCode)
                \(message != nil ? ("Message: " + message!) : "")
                """
        case .appleIDAndPrivacyAcknowledgementRequired:
            return "You must sign in to https://appstoreconnect.apple.com and acknowledge the Apple ID & Privacy agreement."
        case .accountUsesTwoStepAuthentication:
            return "Received a response from Apple that indicates this account has two-step authentication enabled. xcodes currently only supports the newer two-factor authentication, though. Please consider upgrading to two-factor authentication, or explain why this isn't an option for you by making a new feature request in the Help menu."
        case .accountUsesUnknownAuthenticationKind:
            return "Received a response from Apple that indicates this account has two-step or two-factor authentication enabled, but xcodes is unsure how to handle this response. If you continue to have problems, please submit a bug report in the Help menu."
        case let .accountLocked(message):
            return message
        case let .badStatusCode(statusCode, _, _):
            return "Received an unexpected status code: \(statusCode). If you continue to have problems, please submit a bug report in the Help menu."
        case .notDeveloperAppleId:
            return "You are not registered as an Apple Developer.  Please visit Apple Developer Registration. https://developer.apple.com/register/"
        case .notAuthorized:
            return "You are not authorized. Please Sign in with your Apple ID first."
        case let .invalidResult(resultString):
            return resultString ?? "If you continue to have problems, please submit a bug report in the Help menu."
        case .srpInvalidPublicKey:
            return "Invalid Key"
        }
    }
}

public struct AppleSessionData: Equatable, Identifiable {    
    public let serviceKey: String
    public let sessionID: String 
    public let scnt: String
    
    public var id: String { sessionID }

    public init(serviceKey: String, sessionID: String, scnt: String) {
        self.serviceKey = serviceKey
        self.sessionID = sessionID
        self.scnt = scnt
    }
}

struct ServiceKeyResponse: Decodable {
    let authServiceKey: String
}

struct SignInResponse: Decodable {
    let authType: String?
    let serviceErrors: [ServiceError]?
    
    struct ServiceError: Decodable, CustomStringConvertible {
        let code: String
        let message: String
        
        var description: String {
            return "\(code): \(message)"
        }
    }
}

public enum TwoFactorOption: Equatable {
    case smsSent(AuthOptionsResponse.TrustedPhoneNumber)
    case codeSent
    case smsPendingChoice
    case securityKey
}

public struct FSAChallenge: Equatable, Decodable {
    public let challenge: String
    public let keyHandles: [String]
    public let allowedCredentials: String
}

public struct AuthOptionsResponse: Equatable, Decodable {
    public let trustedPhoneNumbers: [TrustedPhoneNumber]?
    public let trustedDevices: [TrustedDevice]?
    public let securityCode: SecurityCodeInfo?
    public let noTrustedDevices: Bool?
    public let serviceErrors: [ServiceError]?
    public let fsaChallenge: FSAChallenge?
    
    public init(
        trustedPhoneNumbers: [AuthOptionsResponse.TrustedPhoneNumber]?, 
        trustedDevices: [AuthOptionsResponse.TrustedDevice]?, 
        securityCode: AuthOptionsResponse.SecurityCodeInfo, 
        noTrustedDevices: Bool? = nil, 
        serviceErrors: [ServiceError]? = nil,
        fsaChallenge: FSAChallenge? = nil
    ) {
        self.trustedPhoneNumbers = trustedPhoneNumbers
        self.trustedDevices = trustedDevices
        self.securityCode = securityCode
        self.noTrustedDevices = noTrustedDevices
        self.serviceErrors = serviceErrors
        self.fsaChallenge = fsaChallenge
    }
    
    public var kind: Kind {
        if trustedDevices != nil {
            return .twoStep
        } else if trustedPhoneNumbers != nil {
            return .twoFactor
        } else if fsaChallenge != nil {
            return .securityKey
        } else {
            return .unknown
        }
    }
    
    // One time with a new testing account I had a response where noTrustedDevices was nil, but the account didn't have any trusted devices.
    // This should have been a situation where an SMS security code was sent automatically.
    // This resolved itself either after some time passed, or by signing into appleid.apple.com with the account.
    // Not sure if it's worth explicitly handling this case or if it'll be really rare.
    public var canFallBackToSMS: Bool {
        noTrustedDevices == true
    }
    
    public var smsAutomaticallySent: Bool {
        trustedPhoneNumbers?.count == 1 && canFallBackToSMS
    }
    
    public struct TrustedPhoneNumber: Equatable, Decodable, Identifiable {
        public let id: Int
        public let numberWithDialCode: String

        public init(id: Int, numberWithDialCode: String) {
            self.id = id
            self.numberWithDialCode = numberWithDialCode
        }
    }
    
    public struct TrustedDevice: Equatable, Decodable {        
        public let id: String
        public let name: String
        public let modelName: String

        public init(id: String, name: String, modelName: String) {
            self.id = id
            self.name = name
            self.modelName = modelName
        }
    }
    
    public struct SecurityCodeInfo: Equatable, Decodable {        
        public let length: Int
        public let tooManyCodesSent: Bool
        public let tooManyCodesValidated: Bool
        public let securityCodeLocked: Bool
        public let securityCodeCooldown: Bool

        public init(
            length: Int,
            tooManyCodesSent: Bool = false,
            tooManyCodesValidated: Bool = false,
            securityCodeLocked: Bool = false,
            securityCodeCooldown: Bool = false
        ) {
            self.length = length
            self.tooManyCodesSent = tooManyCodesSent
            self.tooManyCodesValidated = tooManyCodesValidated
            self.securityCodeLocked = securityCodeLocked
            self.securityCodeCooldown = securityCodeCooldown
        }
    }
    
    public enum Kind: Equatable {
        case twoStep, twoFactor, securityKey, unknown
    }
}

public struct ServiceError: Decodable, Equatable {
    let code: String
    let message: String
}

public enum SecurityCode {
    case device(code: String)
    case sms(code: String, phoneNumberId: Int)
    
    var urlPathComponent: String {
        switch self {
        case .device: return "trusteddevice"
        case .sms: return "phone"
        }
    }
}

/// Object returned from olympus/v1/session
/// Used to check Provider, and show name
/// If Provider is nil, we can assume the Apple User is NOT an Apple Developer and can't download Xcode.
public struct AppleSession: Decodable, Equatable {
    public let user: AppleUser
    public let provider: AppleProvider?
}

public struct AppleProvider: Decodable, Equatable {
    public let providerId: Int
    public let name: String
}

public struct AppleUser: Decodable, Equatable {
    public let fullName: String
}

public struct ServerSRPInitResponse: Decodable {
    let iteration: Int
    let salt: String
    let b: String
    let c: String
    let `protocol`: SRPProtocol
}



extension String {
    func base64ToU8Array() -> Data {
        return Data(base64Encoded: self) ?? Data()
    }
}
extension Data {
    func hexEncodedString() -> String {
        return map { String(format: "%02hhx", $0) }.joined()
    }
}


================================================
FILE: Xcodes/AppleAPI/Sources/AppleAPI/Environment.swift
================================================
import Foundation
import Combine

/**
 Lightweight dependency injection using global mutable state :P

 - SeeAlso: https://www.pointfree.co/episodes/ep16-dependency-injection-made-easy
 - SeeAlso: https://www.pointfree.co/episodes/ep18-dependency-injection-made-comfortable
 - SeeAlso: https://vimeo.com/291588126
 */
public struct Environment {
    public var network = Network()
}

public var Current = Environment()

public struct Network {
    public var session = URLSession.shared

    public var dataTask: (URLRequest) -> URLSession.DataTaskPublisher = { Current.network.session.dataTaskPublisher(for: $0) }
    public func dataTask(with request: URLRequest) -> URLSession.DataTaskPublisher {
        dataTask(request)
    }
}


================================================
FILE: Xcodes/AppleAPI/Sources/AppleAPI/Hashcash.swift
================================================
//
//  Hashcash.swift
//  
//
//  Created by Matt Kiazyk on 2023-02-23.
//

import Foundation
import CryptoKit
import CommonCrypto

/*
# This App Store Connect hashcash spec was generously donated by...
 #
 #                         __  _
 #    __ _  _ __   _ __   / _|(_)  __ _  _   _  _ __  ___  ___
 #   / _` || '_ \ | '_ \ | |_ | | / _` || | | || '__|/ _ \/ __|
 #  | (_| || |_) || |_) ||  _|| || (_| || |_| || |  |  __/\__ \
 #   \__,_|| .__/ | .__/ |_|  |_| \__, | \__,_||_|   \___||___/
 #         |_|    |_|             |___/
 #
 #
*/
public struct Hashcash {
    /// A function to returned a minted hash, using a bit and resource string
    ///
    /**
      X-APPLE-HC: 1:11:20230223170600:4d74fb15eb23f465f1f6fcbf534e5877::6373
                  ^  ^      ^                       ^                     ^
                  |  |      |                       |                     +-- Counter
                  |  |      |                       +-- Resource
                  |  |      +-- Date YYMMDD[hhmm[ss]]
                  |  +-- Bits (number of leading zeros)
                  +-- Version
     
     We can't use an off-the-shelf Hashcash because Apple's implementation is not quite the same as the spec/convention.
     1. The spec calls for a nonce called "Rand" to be inserted between the Ext and Counter. They don't do that at all.
     2. The Counter conventionally encoded as base-64 but Apple just uses the decimal number's string representation.
      
     Iterate from Counter=0 to Counter=N finding an N that makes the SHA1(X-APPLE-HC) lead with Bits leading zero bits
     We get the "Resource" from the X-Apple-HC-Challenge header and Bits from X-Apple-HC-Bits
     */
    /// - Parameters:
    ///    - resource: a string to be used for minting
    ///    - bits: grabbed from `X-Apple-HC-Bits` header
    ///    - date: Default uses Date() otherwise used for testing to check.
    /// - Returns: A String hash to use in `X-Apple-HC` header on /signin
    public func mint(resource: String,
                     bits: UInt = 10,
                     date: String? = nil) -> String? {
        
        let ver = "1"
        
        var ts: String
        if let date = date {
            ts = date
        } else {
            let formatter = DateFormatter()
            formatter.dateFormat = "yyMMddHHmmss"
            ts = formatter.string(from: Date())
        }
        
        let challenge = "\(ver):\(bits):\(ts):\(resource):"
        
        var counter = 0
        
        while true {
            guard let digest = ("\(challenge):\(counter)").sha1 else {
                print("ERROR: Can't generate SHA1 digest")
                return nil
            }
            
            if digest == bits {
                return "\(challenge):\(counter)"
            }
            counter += 1
        }
    }
}

extension String {
    var sha1: Int? {
        
        let data = Data(self.utf8)
        var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
        data.withUnsafeBytes {
            _ = CC_SHA1($0.baseAddress, CC_LONG(data.count), &digest)
        }
        let bigEndianValue = digest.withUnsafeBufferPointer {
                 ($0.baseAddress!.withMemoryRebound(to: UInt32.self, capacity: 1) { $0 })
        }.pointee
        let value = UInt32(bigEndian: bigEndianValue)
        return value.leadingZeroBitCount
    }
}



================================================
FILE: Xcodes/AppleAPI/Sources/AppleAPI/URLRequest+Apple.swift
================================================
import Foundation

public extension URL {
    static let itcServiceKey = URL(string: "https://appstoreconnect.apple.com/olympus/v1/app/config?hostname=itunesconnect.apple.com")!
    static let signIn = URL(string: "https://idmsa.apple.com/appleauth/auth/signin")!
    static let authOptions = URL(string: "https://idmsa.apple.com/appleauth/auth")!
    static let requestSecurityCode = URL(string: "https://idmsa.apple.com/appleauth/auth/verify/phone")!
    static func submitSecurityCode(_ code: SecurityCode) -> URL { URL(string: "https://idmsa.apple.com/appleauth/auth/verify/\(code.urlPathComponent)/securitycode")! }
    static let trust = URL(string: "https://idmsa.apple.com/appleauth/auth/2sv/trust")!
    static let federate = URL(string: "https://idmsa.apple.com/appleauth/auth/federate")!
    static let olympusSession = URL(string: "https://appstoreconnect.apple.com/olympus/v1/session")!
    static let keyAuth = URL(string: "https://idmsa.apple.com/appleauth/auth/verify/security/key")!
    
    static let srpInit = URL(string: "https://idmsa.apple.com/appleauth/auth/signin/init")!
    static let srpComplete = URL(string: "https://idmsa.apple.com/appleauth/auth/signin/complete?isRememberMeEnabled=false")!
    
}

public extension URLRequest {
    static var itcServiceKey: URLRequest {
        return URLRequest(url: .itcServiceKey)
    }

    static func signIn(serviceKey: String, accountName: String, password: String, hashcash: String) -> URLRequest {
        struct Body: Encodable {
            let accountName: String
            let password: String
            let rememberMe = true
        }

        var request = URLRequest(url: .signIn)
        request.allHTTPHeaderFields = request.allHTTPHeaderFields ?? [:]
        request.allHTTPHeaderFields?["Content-Type"] = "application/json"
        request.allHTTPHeaderFields?["X-Requested-With"] = "XMLHttpRequest"
        request.allHTTPHeaderFields?["X-Apple-Widget-Key"] = serviceKey
        request.allHTTPHeaderFields?["X-Apple-HC"] = hashcash
        request.allHTTPHeaderFields?["Accept"] = "application/json, text/javascript"
        request.httpMethod = "POST"
        request.httpBody = try! JSONEncoder().encode(Body(accountName: accountName, password: password))
        return request
    }

    static func authOptions(serviceKey: String, sessionID: String, scnt: String) -> URLRequest {
        var request = URLRequest(url: .authOptions)
        request.allHTTPHeaderFields = request.allHTTPHeaderFields ?? [:]
        request.allHTTPHeaderFields?["X-Apple-ID-Session-Id"] = sessionID
        request.allHTTPHeaderFields?["X-Apple-Widget-Key"] = serviceKey
        request.allHTTPHeaderFields?["scnt"] = scnt
        request.allHTTPHeaderFields?["accept"] = "application/json"
        return request
    }
    
    static func requestSecurityCode(serviceKey: String, sessionID: String, scnt: String, trustedPhoneID: Int) throws -> URLRequest {
        struct Body: Encodable {
            let phoneNumber: PhoneNumber
            let mode = "sms"
            
            struct PhoneNumber: Encodable {
                let id: Int
            }
        }
        
        var request = URLRequest(url: .requestSecurityCode)
        request.allHTTPHeaderFields = request.allHTTPHeaderFields ?? [:]
        request.allHTTPHeaderFields?["Content-Type"] = "application/json"
        request.allHTTPHeaderFields?["X-Apple-ID-Session-Id"] = sessionID
        request.allHTTPHeaderFields?["X-Apple-Widget-Key"] = serviceKey
        request.allHTTPHeaderFields?["scnt"] = scnt
        request.allHTTPHeaderFields?["accept"] = "application/json"
        request.httpMethod = "PUT"
        request.httpBody = try JSONEncoder().encode(Body(phoneNumber: .init(id: trustedPhoneID)))
        return request
    }

    static func submitSecurityCode(serviceKey: String, sessionID: String, scnt: String, code: SecurityCode) throws -> URLRequest {
        struct DeviceSecurityCodeRequest: Encodable {
            let securityCode: SecurityCode
            
            struct SecurityCode: Encodable {
                let code: String
            }
        }

        struct SMSSecurityCodeRequest: Encodable {
            let securityCode: SecurityCode
            let phoneNumber: PhoneNumber
            let mode = "sms"
            
            struct SecurityCode: Encodable {
                let code: String
            }
            struct PhoneNumber: Encodable {
                let id: Int
            }
        }

        var request = URLRequest(url: .submitSecurityCode(code))
        request.allHTTPHeaderFields = request.allHTTPHeaderFields ?? [:]
        request.allHTTPHeaderFields?["X-Apple-ID-Session-Id"] = sessionID
        request.allHTTPHeaderFields?["X-Apple-Widget-Key"] = serviceKey
        request.allHTTPHeaderFields?["scnt"] = scnt
        request.allHTTPHeaderFields?["Accept"] = "application/json"
        request.allHTTPHeaderFields?["Content-Type"] = "application/json"
        request.httpMethod = "POST"
        switch code {
        case .device(let code):
            request.httpBody = try JSONEncoder().encode(DeviceSecurityCodeRequest(securityCode: .init(code: code)))
        case .sms(let code, let phoneNumberId):
            request.httpBody = try JSONEncoder().encode(SMSSecurityCodeRequest(securityCode: .init(code: code), phoneNumber: .init(id: phoneNumberId)))
        }
        return request
    }
    
    static func respondToChallenge(serviceKey: String, sessionID: String, scnt: String, response: Data) -> URLRequest {
        var request = URLRequest(url: .keyAuth)
        request.allHTTPHeaderFields = request.allHTTPHeaderFields ?? [:]
        request.allHTTPHeaderFields?["X-Apple-ID-Session-Id"] = sessionID
        request.allHTTPHeaderFields?["X-Apple-Widget-Key"] = serviceKey
        request.allHTTPHeaderFields?["scnt"] = scnt
        request.allHTTPHeaderFields?["Accept"] = "application/json"
        request.allHTTPHeaderFields?["Content-Type"] = "application/json"
        request.httpMethod = "POST"
        request.httpBody = response
        return request
    }

    static func trust(serviceKey: String, sessionID: String, scnt: String) -> URLRequest {
        var request = URLRequest(url: .trust)
        request.allHTTPHeaderFields = request.allHTTPHeaderFields ?? [:]
        request.allHTTPHeaderFields?["X-Apple-ID-Session-Id"] = sessionID
        request.allHTTPHeaderFields?["X-Apple-Widget-Key"] = serviceKey
        request.allHTTPHeaderFields?["scnt"] = scnt
        request.allHTTPHeaderFields?["Accept"] = "application/json"
        return request
    }

    static var olympusSession: URLRequest {
        return URLRequest(url: .olympusSession)
    }
    
    static func federate(account: String, serviceKey: String) throws -> URLRequest {
        struct FederateRequest: Encodable {
            let accountName: String
            let rememberMe: Bool
        }
        var request = URLRequest(url: .signIn)
        request.allHTTPHeaderFields?["Accept"] = "application/json"
        request.allHTTPHeaderFields?["Content-Type"] = "application/json"
        request.httpMethod = "GET"
        
//        let encoder = JSONEncoder()
//        encoder.outputFormatting = .withoutEscapingSlashes
//        request.httpBody = try encoder.encode(FederateRequest(accountName: account, rememberMe: true))
        
        return request
    }
    
    static func SRPInit(serviceKey: String, a: String, accountName: String) -> URLRequest {
        struct ServerSRPInitRequest: Encodable {
            public let a: String
            public let accountName: String
            public let protocols: [SRPProtocol]
        }
        
        var request = URLRequest(url: .srpInit)
        request.httpMethod = "POST"
        request.allHTTPHeaderFields = request.allHTTPHeaderFields ?? [:]
        request.allHTTPHeaderFields?["Accept"] = "application/json"
        request.allHTTPHeaderFields?["Content-Type"] = "application/json"
        request.allHTTPHeaderFields?["X-Requested-With"] = "XMLHttpRequest"
        request.allHTTPHeaderFields?["X-Apple-Widget-Key"] = serviceKey
        
        request.httpBody = try? JSONEncoder().encode(ServerSRPInitRequest(a: a, accountName: accountName, protocols: [.s2k, .s2k_fo]))
        return request
    }
    
    static func SRPComplete(serviceKey: String, hashcash: String, accountName: String, c: String, m1: String, m2: String) -> URLRequest {
        struct ServerSRPCompleteRequest: Encodable {
            let accountName: String
            let c: String
            let m1: String
            let m2: String
            let rememberMe: Bool
        }
        
        var request = URLRequest(url: .srpComplete)
        request.httpMethod = "POST"
        request.allHTTPHeaderFields = request.allHTTPHeaderFields ?? [:]
        request.allHTTPHeaderFields?["Accept"] = "application/json"
        request.allHTTPHeaderFields?["Content-Type"] = "application/json"
        request.allHTTPHeaderFields?["X-Requested-With"] = "XMLHttpRequest"
        request.allHTTPHeaderFields?["X-Apple-Widget-Key"] = serviceKey
        request.allHTTPHeaderFields?["X-Apple-HC"] = hashcash
        
        request.httpBody = try? JSONEncoder().encode(ServerSRPCompleteRequest(accountName: accountName, c: c, m1: m1, m2: m2, rememberMe: false))
        return request
    }
}

public enum SRPProtocol: String, Codable {
    case s2k, s2k_fo
}




================================================
FILE: Xcodes/AppleAPI/Tests/AppleAPITests/AppleAPITests.swift
================================================
import XCTest
@testable import AppleAPI

final class AppleAPITests: XCTestCase {
    
    func testValidHashCashMint() {
        let bits: UInt = 11
        let resource = "4d74fb15eb23f465f1f6fcbf534e5877"
        let testDate = "20230223170600"
 
        let stamp = Hashcash().mint(resource: resource, bits: bits, date: testDate)
        XCTAssertEqual(stamp, "1:11:20230223170600:4d74fb15eb23f465f1f6fcbf534e5877::6373")
    }
    func testValidHashCashMint2() {
        let bits: UInt = 10
        let resource = "bb63edf88d2f9c39f23eb4d6f0281158"
        let testDate = "20230224001754"
 
        let stamp = Hashcash().mint(resource: resource, bits: bits, date: testDate)
        XCTAssertEqual(stamp, "1:10:20230224001754:bb63edf88d2f9c39f23eb4d6f0281158::866")
    }

    static var allTests = [
        ("testValidHashCashMint", testValidHashCashMint),
        ("testValidHashCashMint2", testValidHashCashMint2),
    ]
}


================================================
FILE: Xcodes/AppleAPI/Tests/AppleAPITests/XCTestManifests.swift
================================================
import XCTest

#if !canImport(ObjectiveC)
public func allTests() -> [XCTestCaseEntry] {
    return [
        testCase(AppleAPITests.allTests),
    ]
}
#endif


================================================
FILE: Xcodes/AppleAPI/Tests/LinuxMain.swift
================================================
import XCTest

import AppleAPITests

var tests = [XCTestCaseEntry]()
tests += AppleAPITests.allTests()
XCTMain(tests)


================================================
FILE: Xcodes/Backend/AppState+Install.swift
================================================
import Combine
import Foundation
import Path
import AppleAPI
import Version
import LegibleError
import os.log
import DockProgress
import XcodesKit

/// Downloads and installs Xcodes
extension AppState {
    
    // check to see if we should auto install for the user
    public func autoInstallIfNeeded() {
        guard let storageValue = Current.defaults.get(forKey: "autoInstallation") as? Int, let autoInstallType = AutoInstallationType(rawValue: storageValue) else { return }

        if autoInstallType == .none { return }
        
        // get newest xcode version
        guard let newestXcode = allXcodes.first, newestXcode.installState == .notInstalled else {
            Logger.appState.info("User has latest Xcode already installed")
            return
        }
        
        if autoInstallType == .newestBeta {
            Logger.appState.info("Auto installing newest Xcode Beta")
            // install it, as user doesn't have it installed and it's either latest beta or latest release
            checkMinVersionAndInstall(id: newestXcode.id)
        } else if autoInstallType == .newestVersion && newestXcode.version.isNotPrerelease {
            Logger.appState.info("Auto installing newest Xcode")
            checkMinVersionAndInstall(id: newestXcode.id)
        } else {
            Logger.appState.info("No new Xcodes version found to auto install")
        }
    }
    
    public func install(_ installationType: InstallationType, downloader: Downloader) -> AnyPublisher<Void, Error> {
        install(installationType, downloader: downloader, attemptNumber: 0)
            .map { _ in Void() }
            .eraseToAnyPublisher()
    }
    
    private func install(_ installationType: InstallationType, downloader: Downloader, attemptNumber: Int) -> AnyPublisher<InstalledXcode, Error> {
        
        Logger.appState.info("Using \(downloader) downloader")
        
        setupDockProgress()
        
        return validateSession()
            .flatMap { _ in
                self.getXcodeArchive(installationType, downloader: downloader)
            }
            .flatMap { xcode, url -> AnyPublisher<InstalledXcode, Swift.Error> in
                self.installArchivedXcode(xcode, at: url)
            }
            .catch { error -> AnyPublisher<InstalledXcode, Swift.Error> in
                self.resetDockProgressTracking()
                
                switch error {
                case InstallationError.damagedXIP(let damagedXIPURL):
                    guard attemptNumber < 1 else { return Fail(error: error).eraseToAnyPublisher() }

                    switch installationType {
                    case .version:
                        // If the XIP was just downloaded, remove it and try to recover.
                        do {
                            Logger.appState.error("\(error.legibleLocalizedDescription)")
                            Logger.appState.info("Removing damaged XIP and re-attempting installation.")
                            try Current.files.removeItem(at: damagedXIPURL)
                            return self.install(installationType, downloader: downloader, attemptNumber: attemptNumber + 1)
                                .eraseToAnyPublisher()
                        } catch {
                            return Fail(error: error)
                                .eraseToAnyPublisher()
                        }
                    }
                default:
                    return Fail(error: error)
                        .eraseToAnyPublisher()
                }
            }
            .handleEvents(receiveOutput: { installedXcode in
                DispatchQueue.main.async {
                    guard let index = self.allXcodes.firstIndex(where: { $0.version.isEquivalent(to: installedXcode.version) }) else { return }
                    self.allXcodes[index].installState = .installed(installedXcode.path)
                }
            })
            .eraseToAnyPublisher()
    }
    
    private func getXcodeArchive(_ installationType: InstallationType, downloader: Downloader) -> AnyPublisher<(AvailableXcode, URL), Error> {
        switch installationType {
        case .version(let availableXcode):
            if let installedXcode = Current.files.installedXcodes(Path.installDirectory).first(where: { $0.version.isEquivalent(to: availableXcode.version) }) {
                return Fail(error: InstallationError.versionAlreadyInstalled(installedXcode))
                    .eraseToAnyPublisher()
            }
            
            return downloadXcode(availableXcode: availableXcode, downloader: downloader)
        }
    }

    private func downloadXcode(availableXcode: AvailableXcode, downloader: Downloader) -> AnyPublisher<(AvailableXcode, URL), Error> {
            self.downloadOrUseExistingArchive(for: availableXcode, downloader: downloader, progressChanged: { [unowned self] progress in
                DispatchQueue.main.async {
                    self.setInstallationStep(of: availableXcode.version, to: .downloading(progress: progress))
                    self.overallProgress.addChild(progress, withPendingUnitCount: AppState.totalProgressUnits - AppState.unxipProgressWeight)
                }
            })
            .map { return (availableXcode, $0) }
            .eraseToAnyPublisher()
    }
    
    public func downloadOrUseExistingArchive(for availableXcode: AvailableXcode, downloader: Downloader, progressChanged: @escaping (Progress) -> Void) -> AnyPublisher<URL, Error> {
        // Check to see if the archive is in the expected path in case it was downloaded but failed to install
        let expectedArchivePath = Path.xcodesApplicationSupport/"Xcode-\(availableXcode.version).\(availableXcode.filename.suffix(fromLast: "."))"
        // aria2 downloads directly to the destination (instead of into /tmp first) so we need to make sure that the download isn't incomplete
        let aria2DownloadMetadataPath = expectedArchivePath.parent/(expectedArchivePath.basename() + ".aria2")
        var aria2DownloadIsIncomplete = false
        if case .aria2 = downloader, aria2DownloadMetadataPath.exists {
            aria2DownloadIsIncomplete = true
        }
        if Current.files.fileExistsAtPath(expectedArchivePath.string), aria2DownloadIsIncomplete == false {
            Logger.appState.info("Found existing archive that will be used for installation at \(expectedArchivePath).")
            return Just(expectedArchivePath.url)
                .setFailureType(to: Error.self)
                .eraseToAnyPublisher()
        }
        else {
            let destination = Path.xcodesApplicationSupport/"Xcode-\(availableXcode.version).\(availableXcode.filename.suffix(fromLast: "."))"
            switch downloader {
            case .aria2:
                let aria2Path = Path(url: Bundle.main.url(forAuxiliaryExecutable: "aria2c")!)!
                return downloadXcodeWithAria2(
                    availableXcode,
                    to: destination,
                    aria2Path: aria2Path,
                    progressChanged: progressChanged
                )
            case .urlSession:
                return downloadXcodeWithURLSession(
                    availableXcode,
                    to: destination,
                    progressChanged: progressChanged
                )
            }
        }
    }
    
    public func downloadXcodeWithAria2(_ availableXcode: AvailableXcode, to destination: Path, aria2Path: Path, progressChanged: @escaping (Progress) -> Void) -> AnyPublisher<URL, Error> {
        let cookies = AppleAPI.Current.network.session.configuration.httpCookieStorage?.cookies(for: availableXcode.url) ?? []
    
        let (progress, publisher) = Current.shell.downloadWithAria2(
            aria2Path, 
            availableXcode.url,
            destination,
            cookies
        )
        progressChanged(progress)
        
        return publisher
            .map { _ in destination.url }
            .eraseToAnyPublisher()
    }

    public func downloadXcodeWithURLSession(_ availableXcode: AvailableXcode, to destination: Path, progressChanged: @escaping (Progress) -> Void) -> AnyPublisher<URL, Error> {
        let resumeDataPath = Path.xcodesApplicationSupport/"Xcode-\(availableXcode.version).resumedata"
        let persistedResumeData = Current.files.contents(atPath: resumeDataPath.string)
        
        return attemptResumableTask(maximumRetryCount: 3) { resumeData -> AnyPublisher<URL, Error> in
            let (progress, publisher) = Current.network.downloadTask(with: availableXcode.url,
                                                                   to: destination.url,
                                                                   resumingWith: resumeData ?? persistedResumeData)
            progressChanged(progress)
            
            return publisher
                .map { $0.saveLocation }
                .eraseToAnyPublisher()
        }
        .handleEvents(receiveCompletion: { completion in
            self.persistOrCleanUpResumeData(at: resumeDataPath, for: completion)
        })
        .eraseToAnyPublisher()
    }

    public func installArchivedXcode(_ availableXcode: AvailableXcode, at archiveURL: URL) -> AnyPublisher<InstalledXcode, Error> {
        unxipProgress.completedUnitCount = 0
        overallProgress.addChild(unxipProgress, withPendingUnitCount: AppState.unxipProgressWeight)
        
        do {
            let destinationURL = Path.installDirectory.join("Xcode-\(availableXcode.version.descriptionWithoutBuildMetadata).app").url
            switch archiveURL.pathExtension {
            case "xip":
                return unarchiveAndMoveXIP(availableXcode: availableXcode, at: archiveURL, to: destinationURL)
                    .tryMap { xcodeURL throws -> InstalledXcode in
                        guard 
                            let path = Path(url: xcodeURL),
                            Current.files.fileExists(atPath: path.string),
                            let installedXcode = InstalledXcode(path: path)
                        else { throw InstallationError.failedToMoveXcodeToApplications }
                        return installedXcode
                    }
                    .flatMap { installedXcode -> AnyPublisher<InstalledXcode, Error> in
                        do {
                            self.setInstallationStep(of: availableXcode.version, to: .trashingArchive)
                            try Current.files.trashItem(at: archiveURL)
                            self.setInstallationStep(of: availableXcode.version, to: .checkingSecurity)
                            
                            return self.verifySecurityAssessment(of: installedXcode)
                                .combineLatest(self.verifySigningCertificate(of: installedXcode.path.url))
                                .map { _ in installedXcode }
                                .eraseToAnyPublisher()
                        } catch {
                            return Fail(error: error)
                                .eraseToAnyPublisher()
                        }
                    }
                    .flatMap { installedXcode -> AnyPublisher<InstalledXcode, Error> in
                        self.setInstallationStep(of: availableXcode.version, to: .finishing)

                        return self.performPostInstallSteps(for: installedXcode)
                            .map { installedXcode }
                            // Show post-install errors but don't fail because of them
                            .handleEvents(receiveCompletion: { [unowned self] completion in
                                if case let .failure(error) = completion {
                                    self.error = error
                                    self.presentedAlert = .generic(title: localizeString("Alert.InstallArchive.Error.Title"), message: error.legibleLocalizedDescription)
                                }
                                resetDockProgressTracking()
                            })
                            .catch { _ in
                                Just(installedXcode)
                                    .setFailureType(to: Error.self)
                                    .eraseToAnyPublisher()
                            }
                            .eraseToAnyPublisher()
                    }
                    .eraseToAnyPublisher()
            case "dmg":
                throw InstallationError.unsupportedFileFormat(extension: "dmg")
            default:
                throw InstallationError.unsupportedFileFormat(extension: archiveURL.pathExtension)
            }
        } catch {
            return Fail(error: error)
                .eraseToAnyPublisher()
        }
    }

    func unarchiveAndMoveXIP(availableXcode: AvailableXcode, at source: URL, to destination: URL) -> AnyPublisher<URL, Swift.Error> {
        self.setInstallationStep(of: availableXcode.version, to: .unarchiving)
        
        return unxipOrUnxipExperiment(source)
            .catch { error -> AnyPublisher<ProcessOutput, Swift.Error> in
                if let executionError = error as? ProcessExecutionError {
                   if executionError.standardError.contains("damaged and can’t be expanded") {
                    return Fail(error: InstallationError.damagedXIP(url: source))
                        .eraseToAnyPublisher()
                   } else if executionError.standardError.contains("can’t be expanded because the selected volume doesn’t have enough free space.") {
                    return Fail(error: InstallationError.notEnoughFreeSpaceToExpandArchive(archivePath: Path(url: source)!,
                                                                                           version: availableXcode.version))
                        .eraseToAnyPublisher()
                   }
                }
                return Fail(error: error)
                    .eraseToAnyPublisher()
            }
        .tryMap { output -> URL in
            self.setInstallationStep(of: availableXcode.version, to: .moving(destination: destination.path))

            let xcodeURL = source.deletingLastPathComponent().appendingPathComponent("Xcode.app")
            let xcodeBetaURL = source.deletingLastPathComponent().appendingPathComponent("Xcode-beta.app")
            if Current.files.fileExists(atPath: xcodeURL.path) {
                try Current.files.moveItem(at: xcodeURL, to: destination)
            }
            else if Current.files.fileExists(atPath: xcodeBetaURL.path) {
                try Current.files.moveItem(at: xcodeBetaURL, to: destination)
            }

            return destination
        }
        .handleEvents(receiveCancel: {
            if Current.files.fileExists(atPath: source.path) {
                try? Current.files.removeItem(source)
            }
            if Current.files.fileExists(atPath: destination.path) {
                try? Current.files.removeItem(destination)
            }
        })
        .eraseToAnyPublisher()
    }
    
    func unxipOrUnxipExperiment(_ source: URL) -> AnyPublisher<ProcessOutput, Error> {
        if unxipExperiment {
            // All hard work done by https://github.com/saagarjha/unxip
            // Compiled to binary with `swiftc -parse-as-library -O unxip.swift`
            return Current.shell.unxipExperiment(source)
        } else {
            return Current.shell.unxip(source)
        }
    }

    public func verifySecurityAssessment(of xcode: InstalledXcode) -> AnyPublisher<Void, Error> {
        return Current.shell.spctlAssess(xcode.path.url)
            .catch { (error: Swift.Error) -> AnyPublisher<ProcessOutput, Error> in
                var output = ""
                if let executionError = error as? ProcessExecutionError {
                    output = [executionError.standardOutput, executionError.standardError].joined(separator: "\n")
                }
                return Fail(error: InstallationError.failedSecurityAssessment(xcode: xcode, output: output))
                    .eraseToAnyPublisher()
            }
            .map { _ in Void() }
            .eraseToAnyPublisher()
    }

    func verifySigningCertificate(of url: URL) -> AnyPublisher<Void, Error> {
        return Current.shell.codesignVerify(url)
            .catch { error -> AnyPublisher<ProcessOutput, Error> in
                var output = ""
                if let executionError = error as? ProcessExecutionError {
                    output = [executionError.standardOutput, executionError.standardError].joined(separator: "\n")
                }
                return Fail(error: InstallationError.codesignVerifyFailed(output: output))
                    .eraseToAnyPublisher()
            }
            .map { output -> CertificateInfo in
                // codesign prints to stderr
                return self.parseCertificateInfo(output.err)
            }
            .tryMap { cert in
                guard
                    cert.teamIdentifier == XcodeTeamIdentifier,
                    cert.authority == XcodeCertificateAuthority
                else { throw InstallationError.unexpectedCodeSigningIdentity(identifier: cert.teamIdentifier, certificateAuthority: cert.authority) }
                
                return Void()
            }
            .eraseToAnyPublisher()
    }

    public struct CertificateInfo {
        public var authority: [String]
        public var teamIdentifier: String
        public var bundleIdentifier: String
    }

    public func parseCertificateInfo(_ rawInfo: String) -> CertificateInfo {
        var info = CertificateInfo(authority: [], teamIdentifier: "", bundleIdentifier: "")

        for part in rawInfo.trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: .newlines) {
            if part.hasPrefix("Authority") {
                info.authority.append(part.components(separatedBy: "=")[1])
            }
            if part.hasPrefix("TeamIdentifier") {
                info.teamIdentifier = part.components(separatedBy: "=")[1]
            }
            if part.hasPrefix("Identifier") {
                info.bundleIdentifier = part.components(separatedBy: "=")[1]
            }
        }

        return info
    }
    
    // MARK: - Post-Install
    
    /// Attemps to install the helper once, then performs all post-install steps
    public func performPostInstallSteps(for xcode: InstalledXcode) {
        performPostInstallSteps(for: xcode)
            .sink(
                receiveCompletion: { completion in
                    if case let .failure(error) = completion {
                        self.error = error
                        self.presentedAlert = .generic(title: localizeString("Alert.PostInstall.Title"), message: error.legibleLocalizedDescription)
                    }
                }, 
                receiveValue: {}
            )
            .store(in: &cancellables)
    }
    
    /// Attemps to install the helper once, then performs all post-install steps
    public func performPostInstallSteps(for xcode: InstalledXcode) -> AnyPublisher<Void, Error> {
        let postInstallPublisher: AnyPublisher<Void, Error> =
            Deferred { [unowned self] in
                self.installHelperIfNecessary()
            }
            .flatMap { [unowned self] in
                self.enableDeveloperMode()
            }
            .flatMap { [unowned self] in
                self.approveLicense(for: xcode)
            }
            .flatMap { [unowned self] in
                self.installComponents(for: xcode)
            }
            .mapError { [unowned self] error in
                Logger.appState.error("Performing post-install steps failed: \(error.legibleLocalizedDescription)")
                return InstallationError.postInstallStepsNotPerformed(version: xcode.version, helperInstallState: self.helperInstallState)
            }
            .eraseToAnyPublisher()

        guard helperInstallState == .installed else {
            // If the helper isn't installed yet then we need to prepare the user for the install prompt,
            // and then actually perform the installation,
            // and the post-install steps need to wait until that is complete.
            // This subject, which completes upon isPreparingUserForActionRequiringHelper being invoked, is used to achieve that.
            // This is not the most straightforward code I've ever written...
            let helperInstallConsentSubject = PassthroughSubject<Void, Error>()

            // Need to dispatch this to avoid duplicate alerts, 
            // the second of which will crash when force-unwrapping isPreparingUserForActionRequiringHelper 
            DispatchQueue.main.async {
                self.isPreparingUserForActionRequiringHelper = { [unowned self] userConsented in
                    if userConsented {
                        helperInstallConsentSubject.send()
                    } else {
                        Logger.appState.info("User did not consent to installing helper during post-install steps.")

                        helperInstallConsentSubject.send(
                            completion: .failure(
                                InstallationError.postInstallStepsNotPerformed(version: xcode.version, helperInstallState: self.helperInstallState)
                            )
                        )
                    }
                }
                self.presentedAlert = .privilegedHelper
            }
            
            unxipProgress.completedUnitCount = AppState.totalProgressUnits
            resetDockProgressTracking()

            return helperInstallConsentSubject
                .flatMap { 
                    postInstallPublisher 
                }
                .eraseToAnyPublisher()
        }
        
        return postInstallPublisher
    }

    private func enableDeveloperMode() -> AnyPublisher<Void, Error> {
        Current.helper.devToolsSecurityEnable()
            .flatMap {
                Current.helper.addStaffToDevelopersGroup()
            }
            .eraseToAnyPublisher()
    }

    private func approveLicense(for xcode: InstalledXcode) -> AnyPublisher<Void, Error> {
        Current.helper.acceptXcodeLicense(xcode.path.string)
            .eraseToAnyPublisher()
    }

    private func installComponents(for xcode: InstalledXcode) -> AnyPublisher<Void, Swift.Error> {
        Current.helper.runFirstLaunch(xcode.path.string)
            .flatMap {
                Current.shell.getUserCacheDir().map { $0.out }
                    .combineLatest(
                        Current.shell.buildVersion().map { $0.out },
                        Current.shell.xcodeBuildVersion(xcode).map { $0.out }
                    )
            }
            .flatMap { cacheDirectory, macOSBuildVersion, toolsVersion in
                Current.shell.touchInstallCheck(cacheDirectory, macOSBuildVersion, toolsVersion)
            }
            .map { _ in Void() }
            .eraseToAnyPublisher()
    }
    
    // MARK: - Dock Progress Tracking
    
    private func setupDockProgress() {
        Task { @MainActor in
            DockProgress.progressInstance = nil
            DockProgress.style = .bar
            
            let progress = Progress(totalUnitCount: AppState.totalProgressUnits)
            progress.kind = .file
            progress.fileOperationKind = .downloading
            overallProgress = progress
            
            DockProgress.progressInstance = overallProgress
        }
        
    }
    
    func resetDockProgressTracking() {
        Task { @MainActor in
            DockProgress.progress = 1 // Only way to completely remove overlay with DockProgress is setting progress to complete
        }
    }
    
    // MARK: - 
    
    func setInstallationStep(of version: Version, to step: XcodeInstallationStep) {
        DispatchQueue.main.async {
            guard let index = self.allXcodes.firstIndex(where: { $0.version.isEquivalent(to: version) }) else { return }
            self.allXcodes[index].installState = .installing(step)
            
            let xcode = self.allXcodes[index]
            Current.notificationManager.scheduleNotification(title: xcode.version.major.description + "." + xcode.version.appleDescription, body: step.description, category: .normal)
        }
    }
    
    func setInstallationStep(of runtime: DownloadableRuntime, to step: RuntimeInstallationStep, postNotification: Bool = true) {
        DispatchQueue.main.async {
            guard let index = self.downloadableRuntimes.firstIndex(where: { $0.identifier == runtime.identifier }) else { return }
            self.downloadableRuntimes[index].installState = .installing(step)
            if postNotification {
                Current.notificationManager.scheduleNotification(title: runtime.name, body: step.description, category: .normal)
            }
        }
    }
}

extension AppState {
    func persistOrCleanUpResumeData<T>(at path: Path, for completion: Subscribers.Completion<T>) {
        switch completion {
        case .finished:
            try? Current.files.removeItem(at: path.url)
        case .failure(let error):
            guard let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data else { return }
            Current.files.createFile(atPath: path.string, contents: resumeData)
        }
    }
}

public enum InstallationError: LocalizedError, Equatable {
    case damagedXIP(url: URL)
    case notEnoughFreeSpaceToExpandArchive(archivePath: Path, version: Version)
    case failedToMoveXcodeToApplications
    case failedSecurityAssessment(xcode: InstalledXcode, output: String)
    case codesignVerifyFailed(output: String)
    case unexpectedCodeSigningIdentity(identifier: String, certificateAuthority: [String])
    case unsupportedFileFormat(extension: String)
    case missingSudoerPassword
    case unavailableVersion(Version)
    case noNonPrereleaseVersionAvailable
    case noPrereleaseVersionAvailable
    case missingUsernameOrPassword
    case versionAlreadyInstalled(InstalledXcode)
    case invalidVersion(String)
    case versionNotInstalled(Version)
    case postInstallStepsNotPerformed(version: Version, helperInstallState: HelperInstallState)

    public var errorDescription: String? {
        switch self {
        case .damagedXIP(let url):
            return String(format: localizeString("InstallationError.DamagedXIP"), url.lastPathComponent)
        case let .notEnoughFreeSpaceToExpandArchive(archivePath, version):
            return String(format: localizeString("InstallationError.NotEnoughFreeSpaceToExpandArchive"), archivePath.basename(), version.appleDescription)
        case .failedToMoveXcodeToApplications:
            return String(format: localizeString("InstallationError.FailedToMoveXcodeToApplications"), Path.installDirectory.string)
        case .failedSecurityAssessment(let xcode, let output):
            return String(format: localizeString("InstallationError.FailedSecurityAssessment"), String(xcode.version), output, xcode.path.string)
        case .codesignVerifyFailed(let output):
            return String(format: localizeString("InstallationError.CodesignVerifyFailed"), output)
        case .unexpectedCodeSigningIdentity(let identity, let certificateAuthority):
            return String(format: localizeString("InstallationError.UnexpectedCodeSigningIdentity"), identity, certificateAuthority, XcodeTeamIdentifier, XcodeCertificateAuthority)
        case .unsupportedFileFormat(let fileExtension):
            return String(format: localizeString("InstallationError.UnsupportedFileFormat"), fileExtension)
        case .missingSudoerPassword:
            return localizeString("InstallationError.MissingSudoerPassword")
        case let .unavailableVersion(version):
            return String(format: localizeString("InstallationError.UnavailableVersion"), version.appleDescription)
        case .noNonPrereleaseVersionAvailable:
            return localizeString("InstallationError.NoNonPrereleaseVersionAvailable")
        case .noPrereleaseVersionAvailable:
            return localizeString("InstallationError.NoPrereleaseVersionAvailable")
        case .missingUsernameOrPassword:
            return localizeString("InstallationError.MissingUsernameOrPassword")
        case let .versionAlreadyInstalled(installedXcode):
            return String(format: localizeString("InstallationError.VersionAlreadyInstalled"), installedXcode.version.appleDescription, installedXcode.path.string)
        case let .invalidVersion(version):
            return String(format: localizeString("InstallationError.InvalidVersion"), version)
        case let .versionNotInstalled(version):
            return String(format: localizeString("InstallationError.VersionNotInstalled"), version.appleDescription)
        case let .postInstallStepsNotPerformed(version, helperInstallState):
            switch helperInstallState {
            case .installed:
                return String(format: localizeString("InstallationError.PostInstallStepsNotPerformed.Installed"), version.appleDescription)
            case .notInstalled, .unknown:
                return String(format: localizeString("InstallationError.PostInstallStepsNotPerformed.NotInstalled"), version.appleDescription)
            }
        }
    }
}

public enum InstallationType {
    case version(AvailableXcode)
}

public enum AutoInstallationType: Int, Identifiable {
    case none = 0
    case newestVersion
    case newestBeta
    
    public var id: Self { self }
    
    public var isAutoInstalling: Bool {
        get {
            return self != .none
        }
        set {
            self = newValue ? .newestVersion : .none
        }
    }
    public var isAutoInstallingBeta: Bool {
        get {
            return self == .newestBeta
        }
        set {
            self = newValue ? .newestBeta : (isAutoInstalling ? .newestVersion : .none)
        }
    }
}

let XcodeTeamIdentifier = "59GAB85EFG"
let XcodeCertificateAuthority = ["Software Signing", "Apple Code Signing Certification Authority", "Apple Root CA"]


================================================
FILE: Xcodes/Backend/AppState+Runtimes.swift
================================================
import Foundation
import XcodesKit
import OSLog
import Combine
import Path
import AppleAPI
import Version

extension AppState {
    func updateDownloadableRuntimes() {
        Task {
            do {
                
                let downloadableRuntimes = try await self.runtimeService.downloadableRuntimes()
                let runtimes = downloadableRuntimes.downloadables.map { runtime in
                    var updatedRuntime = runtime
                    
                    // This loops through and matches up the simulatorVersion to the mappings
                    let simulatorBuildUpdate = downloadableRuntimes.sdkToSimulatorMappings.filter { SDKToSimulatorMapping in
                        SDKToSimulatorMapping.simulatorBuildUpdate == runtime.simulatorVersion.buildUpdate
                    }
                    updatedRuntime.sdkBuildUpdate = simulatorBuildUpdate.map { $0.sdkBuildUpdate }
                    return updatedRuntime
                }
    
                DispatchQueue.main.async {
                    self.downloadableRuntimes = runtimes
                }
                try? cacheDownloadableRuntimes(runtimes)
            } catch {
                Logger.appState.error("Error downloading runtimes: \(error.localizedDescription)")
            }
        }
    }
    
    func updateInstalledRuntimes() {
        Task {
            do {
                Logger.appState.info("Loading Installed runtimes")
                let runtimes = try await self.runtimeService.localInstalledRuntimes()
                
                DispatchQueue.main.async {
                    self.installedRuntimes = runtimes
                }
            } catch {
                Logger.appState.error("Error loading installed runtimes: \(error.localizedDescription)")
            }
        }
    }
    
    func downloadRuntime(runtime: DownloadableRuntime) {
        guard let selectedXcode = self.allXcodes.first(where: { $0.selected }) else {
            Logger.appState.error("No selected Xcode")
            DispatchQueue.main.async {
                self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: "No selected Xcode. Please make an Xcode active")
            }
            return
        }
        // new runtimes
        if runtime.contentType == .cryptexDiskImage {
            // only selected xcodes > 16.1 beta 3 can download runtimes via a xcodebuild -downloadPlatform version
            // only Runtimes coming from cryptexDiskImage can be downloaded via xcodebuild
            if selectedXcode.version > Version(major: 16, minor: 0, patch: 0) {
                
                if runtime.architectures?.isAppleSilicon ?? false {
                    // Need Xcode 26 but with some RC/Beta's its simpler to just to greater > 25
                    if selectedXcode.version > Version(major: 25, minor: 0, patch: 0) {
                        downloadRuntimeViaXcodeBuild(runtime: runtime)
                    } else {
                        // not supported
                        Logger.appState.error("Trying to download a runtime we can't download")
                        DispatchQueue.main.async {
                            self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: localizeString("Alert.Install.Error.Need.Xcode26"))
                        }
                        return
                    }
                    
                } else {
                    downloadRuntimeViaXcodeBuild(runtime: runtime)
                }
            } else {
                // not supported
                Logger.appState.error("Trying to download a runtime we can't download")
                DispatchQueue.main.async {
                    self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: localizeString("Alert.Install.Error.Need.Xcode16.1"))
                }
                return
            }
        } else {
            downloadRuntimeObseleteWay(runtime: runtime)
        }
    }
    
    func downloadRuntimeViaXcodeBuild(runtime: DownloadableRuntime) {
        
        let downloadRuntimeTask = Current.shell.downloadRuntime(runtime.platform.shortName, runtime.simulatorVersion.buildUpdate, runtime.architectures?.isAppleSilicon ?? false ? Architecture.arm64.rawValue : nil)
        
        runtimePublishers[runtime.identifier] = Task { [weak self] in
            guard let self = self else { return }
            do {
                for try await progress in downloadRuntimeTask {
                    if progress.isIndeterminate {
                        DispatchQueue.main.async {
                            self.setInstallationStep(of: runtime, to: .installing, postNotification: false)
                        }
                    } else {
                        DispatchQueue.main.async {
                            self.setInstallationStep(of: runtime, to: .downloading(progress: progress), postNotification: false)
                        }
                    }
                  
                }
                Logger.appState.debug("Done downloading runtime - \(runtime.name)")
                
                DispatchQueue.main.async {
                    guard let index = self.downloadableRuntimes.firstIndex(where: { $0.identifier == runtime.identifier }) else { return }
                    self.downloadableRuntimes[index].installState = .installed
                    self.update()
                }
                
            } catch {
                    Logger.appState.error("Error downloading runtime: \(error.localizedDescription)")
                    DispatchQueue.main.async {
                        self.error = error
                        if let error = error as? String {
                            self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error)
                        } else {
                            self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription)
                        }
                    }
                }
        }
    }
    
    func downloadRuntimeObseleteWay(runtime: DownloadableRuntime) {
        runtimePublishers[runtime.identifier] = Task {
            do {
                let downloadedURL = try await downloadRunTimeFull(runtime: runtime)
                if !Task.isCancelled {
                    Logger.appState.debug("Installing runtime: \(runtime.name)")
                    DispatchQueue.main.async {
                        self.setInstallationStep(of: runtime, to: .installing)
                    }
                    switch runtime.contentType {
                    case .cryptexDiskImage:
                        // not supported yet (do we need to for old packages?)
                        throw "Installing via cryptexDiskImage not support - please install manually from \(downloadedURL.description)"
                    case .package:
                        // not supported yet (do we need to for old packages?)
                        throw "Installing via package not support - please install manually from \(downloadedURL.description)"
                    case .diskImage:
                        try await self.installFromImage(dmgURL: downloadedURL)
                        DispatchQueue.main.async {
                            self.setInstallationStep(of: runtime, to: .trashingArchive)
                        }
                        try Current.files.removeItem(at: downloadedURL)
                    }
                
                    DispatchQueue.main.async {
                        guard let index = self.downloadableRuntimes.firstIndex(where: { $0.identifier == runtime.identifier }) else { return }
                        self.downloadableRuntimes[index].installState = .installed
                    }
                    updateInstalledRuntimes()
                }
              
            }
            catch {
                Logger.appState.error("Error downloading runtime: \(error.localizedDescription)")
                DispatchQueue.main.async {
                    self.error = error
                    if let error = error as? String {
                        self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error)
                    } else {
                        self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription)
                    }
                }
            }
        }
    }
    
    func downloadRunTimeFull(runtime: DownloadableRuntime) async throws -> URL {
        guard let source = runtime.source else {
            throw "Invalid runtime source"
        }
        
        guard let downloadPath = runtime.downloadPath else {
            throw "Invalid runtime downloadPath"
        }
    
        // sets a proper cookie for runtimes
        try await validateADCSession(path: downloadPath)
        
        let downloader = Downloader(rawValue: Current.defaults.string(forKey: "downloader") ?? "aria2") ?? .aria2
        
        let url = URL(string: source)!
        let expectedRuntimePath = Path.xcodesApplicationSupport/"\(url.lastPathComponent)"
        // aria2 downloads directly to the destination (instead of into /tmp first) so we need to make sure that the download isn't incomplete
        let aria2DownloadMetadataPath = expectedRuntimePath.parent/(expectedRuntimePath.basename() + ".aria2")
        var aria2DownloadIsIncomplete = false
        if case .aria2 = downloader, aria2DownloadMetadataPath.exists {
            aria2DownloadIsIncomplete = true
        }
        if Current.files.fileExistsAtPath(expectedRuntimePath.string), aria2DownloadIsIncomplete == false {
            Logger.appState.info("Found existing runtime that will be used for installation at \(expectedRuntimePath).")
            return expectedRuntimePath.url
        }
        
        Logger.appState.info("Downloading \(runtime.visibleIdentifier) with \(downloader)")
        switch downloader {
        case .aria2:
            let aria2Path = Path(url: Bundle.main.url(forAuxiliaryExecutable: "aria2c")!)!
                for try await progress in downloadRuntimeWithAria2(runtime, to: expectedRuntimePath, aria2Path: aria2Path) {
                    DispatchQueue.main.async {
                        self.setInstallationStep(of: runtime, to: .downloading(progress: progress), postNotification: false)
                    }
                }
                Logger.appState.debug("Done downloading runtime")

        case .urlSession:
            throw "Downloading runtimes with URLSession is not supported. Please use aria2"
        }
        return expectedRuntimePath.url
    }

    public func downloadRuntimeWithAria2(_ runtime: DownloadableRuntime, to destination: Path, aria2Path: Path) -> AsyncThrowingStream<Progress, Error> {
        guard let url = runtime.url else {
            return AsyncThrowingStream<Progress, Error> { continuation in
                continuation.finish(throwing: "Invalid or non existant runtime url")
            }
        }
        
        let cookies = AppleAPI.Current.network.session.configuration.httpCookieStorage?.cookies(for: url) ?? []
    
        return Current.shell.downloadWithAria2Async(aria2Path, url, destination, cookies)
    }
    
    
    public func installFromImage(dmgURL: URL) async throws {
        try await self.runtimeService.installRuntimeImage(dmgURL: dmgURL)
    }
    
    func cancelRuntimeInstall(runtime: DownloadableRuntime) {
        // Cancel the publisher
        
        runtimePublishers[runtime.identifier]?.cancel()
        runtimePublishers[runtime.identifier] = nil
        
        // If the download is cancelled by the user, clean up the download files that aria2 creates.
        guard let source = runtime.source else {
            return
        }
        let url = URL(string: source)!
        let expectedRuntimePath = Path.xcodesApplicationSupport/"\(url.lastPathComponent)"
        let aria2DownloadMetadataPath = expectedRuntimePath.parent/(expectedRuntimePath.basename() + ".aria2")
   
        try? Current.files.removeItem(at: expectedRuntimePath.url)
        try? Current.files.removeItem(at: aria2DownloadMetadataPath.url)
        
        guard let index = self.downloadableRuntimes.firstIndex(where: { $0.identifier == runtime.identifier }) else { return }
        self.downloadableRuntimes[index].installState = .notInstalled
        
        updateInstalledRuntimes()
    }
    
    func runtimeInstallPath(xcode: Xcode, runtime: DownloadableRuntime) -> Path? {
        if let coreSimulatorInfo = coreSimulatorInfo(runtime: runtime) {
            let urlString = coreSimulatorInfo.path["relative"]!
            // app was not allowed to open up file:// url's so remove
            let fileRemovedString = urlString.replacingOccurrences(of: "file://", with: "")
            let url = URL(fileURLWithPath: fileRemovedString)
            
            return Path(url: url)!
        }
        return nil
    }
    
    func coreSimulatorInfo(runtime: DownloadableRuntime) -> CoreSimulatorImage? {
        return installedRuntimes.filter({
            $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate &&
            ((runtime.architectures ?? []).isEmpty ? true :
            $0.runtimeInfo.supportedArchitectures == runtime.architectures )}).first
    }
    
    func deleteRuntime(runtime: DownloadableRuntime) async throws {
        if let info = coreSimulatorInfo(runtime: runtime) {
            try await runtimeService.deleteRuntime(identifier: info.uuid)
            
            // give it some time to actually finish deleting before updating
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
                self?.updateInstalledRuntimes()
            }
        } else {
            throw "No simulator found with \(runtime.identifier)"
        }
    }
}

extension AnyPublisher {
    func async() async throws -> Output {
        try await withCheckedThrowingContinuation { continuation in
            var cancellable: AnyCancellable?
            
            cancellable = first()
                .sink { result in
                    switch result {
                    case .finished:
                        break
                    case let .failure(error):
                        continuation.resume(throwing: error)
                    }
                    cancellable?.cancel()
                } receiveValue: { value in
                    continuation.resume(with: .success(value))
                }
        }
    }
}
extension AnyPublisher where Failure: Error {
    struct Subscriber {
        fileprivate let send: (Output) -> Void
        fileprivate let complete: (Subscribers.Completion<Failure>) -> Void

        func send(_ value: Output) { self.send(value) }
        func send(completion: Subscribers.Completion<Failure>) { self.complete(completion) }
    }

    init(_ closure: (Subscriber) -> AnyCancellable) {
        let subject = PassthroughSubject<Output, Failure>()

        let subscriber = Subscriber(
            send: subject.send,
            complete: subject.send(completion:)
        )
        let cancel = closure(subscriber)

        self = subject
            .handleEvents(receiveCancel: cancel.cancel)
            .eraseToAnyPublisher()
    }
}

extension AnyPublisher where Failure == Error {
    init(taskPriority: TaskPriority? = nil, asyncFunc: @escaping () async throws -> Output) {
        self.init { subscriber in
            let task = Task(priority: taskPriority) {
                do {
                    subscriber.send(try await asyncFunc())
                    subscriber.send(completion: .finished)
                } catch {
                    subscriber.send(completion: .failure(error))
                }
            }
            return AnyCancellable { task.cancel() }
        }
    }
}


================================================
FILE: Xcodes/Backend/AppState+Update.swift
================================================
import Combine
import Foundation
import Path
import Version
import SwiftSoup
import AppleAPI
import XcodesKit

extension AppState {
    
    var isReadyForUpdate: Bool {
        guard let lastUpdated = Current.defaults.date(forKey: "lastUpdated"),
          // This is bad date math but for this use case it doesn't need to be exact
          lastUpdated < Current.date().addingTimeInterval(-60 * 60 * 5)
        else {
            return false
       }
        return true
    }
    
    func updateIfNeeded() {
        guard
            isReadyForUpdate
        else { 
            updatePublisher = updateSelectedXcodePath()
                .sink(
                    receiveCompletion: { _ in 
                        self.updatePublisher = nil
                    },
                    receiveValue: { _ in }
                )
            return
        }
        update() as Void
    }

    func update() {
        guard !isUpdating else { return }
        updateDownloadableRuntimes()
        updateInstalledRuntimes()
        updatePublisher = updateSelectedXcodePath()
            .flatMap { _ in 
                self.updateAvailableXcodes(from: self.dataSource)
            }
            .sink(
                receiveCompletion: { [unowned self] completion in
                    switch completion {
                    case let .failure(error):
                        // Prevent setting the app state error if it is an invalid session, we will present the sign in view instead
                        if error as? AuthenticationError != .invalidSession {
                            self.error = error
                            self.presentedAlert = .generic(title: localizeString("Alert.Update.Error.Title"), message: error.legibleLocalizedDescription)
                        }
                    case .finished:
                        Current.defaults.setDate(Current.date(), forKey: "lastUpdated")
                    }

                    self.updatePublisher = nil
                },
                receiveValue: { _ in }
            )
    }
    
    func updateSelectedXcodePath() -> AnyPublisher<Void, Never> {
        Current.shell.xcodeSelectPrintPath()
            .handleEvents(receiveOutput: { output in self.selectedXcodePath = output.out })
            // Ignore xcode-select failures
            .map { _ in Void() }
            .catch { _ in Just(()) }
            .eraseToAnyPublisher()
    }

    private func updateAvailableXcodes(from dataSource: DataSource) -> AnyPublisher<[AvailableXcode], Error> {
        switch dataSource {
        case .apple:
            return signInIfNeeded()
                .flatMap { [unowned self] in
                    // this will check to see if the Apple ID is a valid Apple Developer or not.
                    // If it's not, we can't use the Apple source to get xcode info.
                    self.validateSession()
                }
                .flatMap { [unowned self] in self.releasedXcodes().combineLatest(self.prereleaseXcodes()) }
                .receive(on: DispatchQueue.main)
                .map { releasedXcodes, prereleaseXcodes in
                    // Starting with Xcode 11 beta 6, developer.apple.com/download and developer.apple.com/download/more both list some pre-release versions of Xcode.
                    // Previously pre-release versions only appeared on developer.apple.com/download.
                    // /download/more doesn't include build numbers, so we trust that if the version number and prerelease identifiers are the same that they're the same build.
                    // If an Xcode version is listed on both sites then prefer the one on /download because the build metadata is used to compare against installed Xcodes.
                    let xcodes = releasedXcodes.filter { releasedXcode in
                        prereleaseXcodes.contains { $0.version.isEquivalent(to: releasedXcode.version) } == false
                    } + prereleaseXcodes
                    return xcodes
                }
                .handleEvents(
                    receiveOutput: { xcodes in
                        self.availableXcodes = xcodes
                        try? self.cacheAvailableXcodes(xcodes)
                    }
                )
                .eraseToAnyPublisher()
        case .xcodeReleases:
            return xcodeReleases()
                .receive(on: DispatchQueue.main)
                .handleEvents(
                    receiveOutput: { xcodes in
                        self.availableXcodes = xcodes
                        try? self.cacheAvailableXcodes(xcodes)    
                    }
                )
                .eraseToAnyPublisher()
        }
    }
}

extension AppState {
    // MARK: - Available Xcode Cache

    func loadCachedAvailableXcodes() throws {
        guard let data = Current.files.contents(atPath: Path.cacheFile.string) else { return }
        let xcodes = try JSONDecoder().decode([AvailableXcode].self, from: data)
        self.availableXcodes = xcodes
    }

    func cacheAvailableXcodes(_ xcodes: [AvailableXcode]) throws {
        let data = try JSONEncoder().encode(xcodes)
        try FileManager.default.createDirectory(at: Path.cacheFile.url.deletingLastPathComponent(),
                                                withIntermediateDirectories: true)
        try data.write(to: Path.cacheFile.url)
    }
    
    // MARK: Runtime Cache
    
    func loadCacheDownloadableRuntimes() throws {
        guard let data = Current.files.contents(atPath: Path.runtimeCacheFile.string) else { return }
        let runtimes = try JSONDecoder().decode([DownloadableRuntime].self, from: data)
        self.downloadableRuntimes = runtimes
    }
    
    func cacheDownloadableRuntimes(_ runtimes: [DownloadableRuntime]) throws {
        let data = try JSONEncoder().encode(runtimes)
        try FileManager.default.createDirectory(at: Path.runtimeCacheFile.url.deletingLastPathComponent(),
                                                withIntermediateDirectories: true)
        try data.write(to: Path.runtimeCacheFile.url)
    }
}

extension AppState {
    // MARK: - Apple
    
    private func releasedXcodes() -> AnyPublisher<[AvailableXcode], Swift.Error> {
        Current.network.dataTask(with: URLRequest.downloads)
            .map(\.data)
            .decode(type: Downloads.self, decoder: configure(JSONDecoder()) {
                $0.dateDecodingStrategy = .formatted(.downloadsDateModified)
            })
            .tryMap { downloads -> [AvailableXcode] in
                if downloads.hasError {
                    throw AuthenticationError.invalidResult(resultString: downloads.resultsString)
                }
                guard let downloadList = downloads.downloads else {
                    throw AuthenticationError.invalidResult(resultString: localizeString("DownloadingError"))
                }
                let xcodes = downloadList
                    .filter { $0.name.range(of: "^Xcode [0-9]", options: .regularExpression) != nil }
                    .compactMap { download -> AvailableXcode? in
                        let urlPrefix = URL(string: "https://download.developer.apple.com/")!
                        guard 
                            let xcodeFile = download.files.first(where: { $0.remotePath.hasSuffix("dmg") || $0.remotePath.hasSuffix("xip") }),
                            let version = Version(xcodeVersion: download.name)
                        else { return nil }

                        let url = urlPrefix.appendingPathComponent(xcodeFile.remotePath)
                        return AvailableXcode(version: version, url: url, filename: String(xcodeFile.remotePath.suffix(fromLast: "/")), releaseDate: download.dateModified, fileSize: xcodeFile.fileSize)
                    }
                return xcodes
            }
            .eraseToAnyPublisher()
    }

    private func prereleaseXcodes() -> AnyPublisher<[AvailableXcode], Error> {
        Current.network.dataTask(with: URLRequest.download)
            .tryMap { (data, _) -> [AvailableXcode] in
                try self.parsePrereleaseXcodes(from: data)
            }
            .eraseToAnyPublisher()
    }

    private func parsePrereleaseXcodes(from data: Data) throws -> [AvailableXcode] {
        let body = String(data: data, encoding: .utf8)!
        let document = try SwiftSoup.parse(body)

        guard 
            let xcodeHeader = try document.select("h2:containsOwn(Xcode)").first(),
            let productBuildVersion = try xcodeHeader.parent()?.select("li:contains(Build)").text().replacingOccurrences(of: "Build", with: ""),
            let releaseDateString = try xcodeHeader.parent()?.select("li:contains(Released)").text().replacingOccurrences(of: "Released", with: ""),
            let version = Version(xcodeVersion: try xcodeHeader.text(), buildMetadataIdentifier: productBuildVersion),
            let path = try document.select(".direct-download[href*=xip]").first()?.attr("href"),
            let url = URL(string: "https://developer.apple.com" + path)
        else { return [] }

        let filename = String(path.suffix(fromLast: "/"))

        return [AvailableXcode(version: version, url: url, filename: filename, releaseDate: DateFormatter.downloadsReleaseDate.date(from: releaseDateString))]
    }
}

extension AppState {
    // MARK: - XcodeReleases
    
    private func xcodeReleases() -> AnyPublisher<[AvailableXcode], Error> {
        Current.network.dataTask(with: URLRequest(url: URL(string: "https://xcodereleases.com/data.json")!))
            .map(\.data)
            .decode(type: [XcodeRelease].self, decoder: JSONDecoder())
            .map { xcReleasesXcodes in
                let xcodes = xcReleasesXcodes.compactMap { xcReleasesXcode -> AvailableXcode? in
                    guard
                        let downloadURL = xcReleasesXcode.links?.download?.url,
                        let version = Version(xcReleasesXcode: xcReleasesXcode)
                    else { return nil }
                    
                    let releaseDate = Calendar(identifier: .gregorian).date(from: DateComponents(
                        year: xcReleasesXcode.date.year,
                        month: xcReleasesXcode.date.month,
                        day: xcReleasesXcode.date.day
                    ))
                    
                    return AvailableXcode(
                        version: version,
                        url: downloadURL,
                        filename: String(downloadURL.path.suffix(fromLast: "/")),
                        releaseDate: releaseDate,
                        requiredMacOSVersion: xcReleasesXcode.requires,
                        releaseNotesURL: xcReleasesXcode.links?.notes?.url,
                        sdks: xcReleasesXcode.sdks,
                        compilers: xcReleasesXcode.compilers,
                        architectures: xcReleasesXcode.architectures
                    )
                }
                return xcodes
            }
            .eraseToAnyPublisher()
    }
}


================================================
FILE: Xcodes/Backend/AppState.swift
================================================
import AppKit
import AppleAPI
import Combine
import Path
import LegibleError
import KeychainAccess
import Path
import Version
import os.log
import DockProgress
import XcodesKit
import LibFido2Swift

enum PreferenceKey: String {
    case installPath
    case localPath
    case unxipExperiment
    case createSymLinkOnSelect
    case onSelectActionType
    case showOpenInRosettaOption
    case autoInstallation
    case SUEnableAutomaticChecks
    case includePrereleaseVersions
    case downloader
    case dataSource
    case xcodeListCategory
    case allowedMajorVersions
    case hideSupportXcodes
    case xcodeListArchitectures

    func isManaged() -> Bool { UserDefaults.standard.objectIsForced(forKey: self.rawValue) }
}

class AppState: ObservableObject {
    private let client = AppleAPI.Client()
    internal let runtimeService = RuntimeService()
   
    // MARK: - Published Properties
    
    @Published var authenticationState: AuthenticationState = .unauthenticated
    @Published var availableXcodes: [AvailableXcode] = [] {
        willSet {
            if newValue.count > availableXcodes.count && availableXcodes.count != 0 {
                Current.notificationManager.scheduleNotification(title: localizeString("Notification.NewXcodeVersion.Title"), body: localizeString("Notification.NewXcodeVersion.Body"), category: .normal)
            }
            updateAllXcodes(
                availableXcodes: newValue, 
                installedXcodes: Current.files.installedXcodes(Path.installDirectory), 
                selectedXcodePath: selectedXcodePath
            )
        }
        didSet {
            autoInstallIfNeeded()
        }
    }
    @Published var allXcodes: [Xcode] = []
    @Published var selectedXcodePath: String? {
        willSet {
            updateAllXcodes(
                availableXcodes: availableXcodes,
                installedXcodes: Current.files.installedXcodes(Path.installDirectory),
                selectedXcodePath: newValue
            )
        }
    }
    @Published var updatePublisher: AnyCancellable?
    var isUpdating: Bool { updatePublisher != nil }
    @Published var presentedSheet: XcodesSheet? = nil
    @Published var isProcessingAuthRequest = false
    @Published var xcodeBeingConfirmedForUninstallation: Xcode?
    @Published var presentedAlert: XcodesAlert?
    @Published var presentedPreferenceAlert: XcodesPreferencesAlert?
    @Published var helperInstallState: HelperInstallState = .notInstalled
    /// Whether the user is being prepared for the helper installation alert with an explanation.
    /// This closure will be performed after the user chooses whether or not to proceed.
    @Published var isPreparingUserForActionRequiringHelper: ((Bool) -> Void)?

    // MARK: - Errors

    @Published var error: Error?
    @Published var authError: Error?
    
    // MARK: Advanced Preferences
    @Published var localPath = "" {
        didSet {
            Current.defaults.set(localPath, forKey: "localPath")
        }
    }
    
    var disableLocalPathChange: Bool { PreferenceKey.localPath.isManaged() }

    @Published var installPath = "" {
        didSet {
            Current.defaults.set(installPath, forKey: "installPath")
        }
    }

    var disableInstallPathChange: Bool { PreferenceKey.installPath.isManaged() }

    @Published var unxipExperiment = false {
        didSet {
            Current.defaults.set(unxipExperiment, forKey: "unxipExperiment")
        }
    }
    
    var disableUnxipExperiment: Bool { PreferenceKey.unxipExperiment.isManaged() }

    @Published var createSymLinkOnSelect = false {
        didSet {
            Current.defaults.set(createSymLinkOnSelect, forKey: "createSymLinkOnSelect")
        }
    }
    
    var createSymLinkOnSelectDisabled: Bool {
        return onSelectActionType == .rename || PreferenceKey.createSymLinkOnSelect.isManaged()
    }
    
    @Published var onSelectActionType = SelectedActionType.none {
        didSet {
            Current.defaults.set(onSelectActionType.rawValue, forKey: "onSelectActionType")
            
            if onSelectActionType == .rename {
                createSymLinkOnSelect = false
            }
        }
    }
    
    var onSelectActionTypeDisabled: Bool { PreferenceKey.onSelectActionType.isManaged() }

    @Published var showOpenInRosettaOption = false {
        didSet {
            Current.defaults.set(showOpenInRosettaOption, forKey: "showOpenInRosettaOption")
        }
    }
    
    @Published var terminateAfterLastWindowClosed = false {
        didSet {
            Current.defaults.set(terminateAfterLastWindowClosed, forKey: "terminateAfterLastWindowClosed")
        }
    }
    
    // MARK: - Runtimes
    
    @Published var downloadableRuntimes: [DownloadableRuntime] = []
    @Published var installedRuntimes: [CoreSimulatorImage] = []

    // MARK: - Publisher Cancellables
    
    var cancellables = Set<AnyCancellable>()
    private var installationPublishers: [XcodeID: AnyCancellable] = [:]
    internal var runtimePublishers: [String: Task<(), any Error>] = [:]
    private var selectPublisher: AnyCancellable?
    private var uninstallPublisher: AnyCancellable?
    private var autoInstallTimer: Timer?
    
    // MARK: - Dock Progress Tracking
    
    public static let totalProgressUnits = Int64(10)
    public static let unxipProgressWeight = Int64(1)
    var overallProgress = Progress()
    var unxipProgress = {
        let progress = Progress(totalUnitCount: totalProgressUnits)
        progress.kind = .file
        progress.fileOperationKind = .copying
        return progress
    }()
    
    // MARK: - 
    
    var dataSource: DataSource {
        Current.defaults.string(forKey: "dataSource").flatMap(DataSource.init(rawValue:)) ?? .default
    }

    var savedUsername: String? {
        Current.defaults.string(forKey: "username")
    }

    var hasSavedUsername: Bool {
        savedUsername != nil
    }
    
    var bottomStatusBarMessage: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "dd/MM/yyyy"
        let finishDate = formatter.date(from: "11/06/2022")
        
        if Date().compare(finishDate!) == .orderedAscending {
            return String(format: localizeString("WWDC.Message"), "2022")
        }
        return ""
    }
    
    // MARK: - Init
    
    init() {
        guard !isTesting else { return }
        try? loadCachedAvailableXcodes()
        try? loadCacheDownloadableRuntimes()
        checkIfHelperIsInstalled()
        setupAutoInstallTimer()
        setupDefaults()
    }
    
    func setupDefaults() {
        localPath = Current.defaults.string(forKey: "localPath") ?? Path.defaultXcodesApplicationSupport.string
        unxipExperiment = Current.defaults.bool(forKey: "unxipExperiment") ?? false
        createSymLinkOnSelect = Current.defaults.bool(forKey: "createSymLinkOnSelect") ?? false
        onSelectActionType = SelectedActionType(rawValue: Current.defaults.string(forKey: "onSelectActionType") ?? "none") ?? .none
        installPath = Current.defaults.string(forKey: "installPath") ?? Path.defaultInstallDirectory.string
        showOpenInRosettaOption = Current.defaults.bool(forKey: "showOpenInRosettaOption") ?? false
        terminateAfterLastWindowClosed = Current.defaults.bool(forKey: "terminateAfterLastWindowClosed") ?? false
    }
    
    // MARK: Timer
    /// Runs a timer every 6 hours when app is open to check if it needs to auto install any xcodes
    func setupAutoInstallTimer() {
        guard let storageValue = Current.defaults.get(forKey: "autoInstallation") as? Int, let autoInstallType = AutoInstallationType(rawValue: storageValue) else { return }

        if autoInstallType == .none { return }
        
        autoInstallTimer = Timer.scheduledTimer(withTimeInterval: 60*60*6, repeats: true) { [weak self] _ in
            self?.updateIfNeeded()
        }
    }
    // MARK: - Authentication
    
    func validateADCSession(path: String) -> AnyPublisher<Void, Error> {
        return Current.network.dataTask(with: URLRequest.downloadADCAuth(path: path))
            .receive(on: DispatchQueue.main)
            .tryMap { result -> Void in
                let httpResponse = result.response as! HTTPURLResponse
                if httpResponse.statusCode == 401 {
                    throw AuthenticationError.notAuthorized
                }
            }
            .eraseToAnyPublisher()
    }
    
    func validateADCSession(path: String) async throws {
        let result = try await Current.network.dataTaskAsync(with: URLRequest.downloadADCAuth(path: path))
        let httpResponse = result.1 as! HTTPURLResponse
        if httpResponse.statusCode == 401 {
            throw AuthenticationError.notAuthorized
        }
    }
    
    func validateSession() -> AnyPublisher<Void, Error> {
        
        return Current.network.validateSession()
            .receive(on: DispatchQueue.main)
            .handleEvents(receiveCompletion: { completion in 
                if case .failure = completion {
                    // this is causing some awkwardness with showing an alert with the error and also popping up the sign in view
                    // self.authenticationState = .unauthenticated
                    // self.presentedSheet = .signIn
                }
            })
            .eraseToAnyPublisher()
    }
    
    func signInIfNeeded() -> AnyPublisher<Void, Error> {
        validateSession()
            .catch { (error) -> AnyPublisher<Void, Error> in
                guard
                    let username = self.savedUsername,
                    let password = try? Current.keychain.getString(username)
                else {
                    return Fail(error: error) 
                        .eraseToAnyPublisher()
                }

                return self.signIn(username: username, password: password)
                    .map { _ in Void() }
                    .eraseToAnyPublisher()
            }
            .eraseToAnyPublisher()
    }
    
    func signIn(username: String, password: String) {
        authError = nil
        signIn(username: username.lowercased(), password: password)
            .sink(
                receiveCompletion: { _ in },
                receiveValue: { _ in }
            )
            .store(in: &cancellables)
    }
    
    func signIn(username: String, password: String) -> AnyPublisher<AuthenticationState, Error> {
        try? Current.keychain.set(password, key: username)
        Current.defaults.set(username, forKey: "username")
        
        isProcessingAuthRequest = true
        return client.srpLogin(accountName: username, password: password)
            .receive(on: DispatchQueue.main)
            .handleEvents(
                receiveOutput: { authenticationState in 
                    self.authenticationState = authenticationState
                },
                receiveCompletion: { completion in
                    self.handleAuthenticationFlowCompletion(completion)
                    self.isProcessingAuthRequest = false
                }
            )
            .eraseToAnyPublisher()
    }
    
    func handleTwoFactorOption(_ option: TwoFactorOption, authOptions: AuthOptionsResponse, serviceKey: String, sessionID: String, scnt: String) {
        let sessionData = AppleSessionData(serviceKey: serviceKey, sessionID: sessionID, scnt: scnt)

        if option == .securityKey, fido2DeviceIsPresent() && !fido2DeviceNeedsPin() {
            createAndSubmitSecurityKeyAssertationWithPinCode(nil, sessionData: sessionData, authOptions: authOptions)
        } else {
            self.presentedSheet = .twoFactor(.init(
                option: option,
                authOptions: authOptions,
                sessionData: sessionData
            ))
        }
    }

    func requestSMS(to trustedPhoneNumber: AuthOptionsResponse.TrustedPhoneNumber, authOptions: AuthOptionsResponse, sessionData: AppleSessionData) {        
        isProcessingAuthRequest = true
        client.requestSMSSecurityCode(to: trustedPhoneNumber, authOptions: authOptions, sessionData: sessionData)
            .receive(on: DispatchQueue.main)
            .sink(
                receiveCompletion: { completion in
                    self.handleAuthenticationFlowCompletion(completion)
                    self.isProcessingAuthRequest = false
                }, 
                receiveValue: { authenticationState in 
                    self.authenticationState = authenticationState
                    if case let AuthenticationState.waitingForSecondFactor(option, authOptions, sessionData) = authenticationState {
                        self.handleTwoFactorOption(option, authOptions: authOptions, serviceKey: sessionData.serviceKey, sessionID: sessionData.sessionID, scnt: sessionData.scnt)
                    }
                }
            )
            .store(in: &cancellables)
    }
    
    func choosePhoneNumberForSMS(authOptions: AuthOptionsResponse, sessionData: AppleSessionData) {
        self.presentedSheet = .twoFactor(.init(
            option: .smsPendingChoice,
            authOptions: authOptions,
            sessionData: sessionData
        ))
    }
    
    func submitSecurityCode(_ code: SecurityCode, sessionData: AppleSessionData) {
        isProcessingAuthRequest = true
        client.submitSecurityCode(code, sessionData: sessionData)
            .receive(on: DispatchQueue.main)
            .sink(
                receiveCompletion: { completion in
                    self.handleAuthenticationFlowCompletion(completion)
                    self.isProcessingAuthRequest = false
                },
                receiveValue: { authenticationState in
                    self.authenticationState = authenticationState
                }
            )
            .store(in: &cancellables)
    }
    
    private lazy var fido2 = FIDO2()

    func createAndSubmitSecurityKeyAssertationWithPinCode(_ pinCode: String?, sessionData: AppleSessionData, authOptions: AuthOptionsResponse) {
        self.presentedSheet = .securityKeyTouchToConfirm
        
        guard let fsaChallenge = authOptions.fsaChallenge else {
            // This shouldn't happen
            // we shouldn't have called this method without setting the fsaChallenge
            // so this is an assertionFailure
            assertionFailure()
            self.authError = "Something went wrong. Please file a bug report"
            return
        }
        
        // The challenge is encoded in Base64URL encoding
        let challengeUrl = fsaChallenge.challenge
        let challenge = FIDO2.base64urlToBase64(base64url: challengeUrl)
        let origin = "https://idmsa.apple.com"
        let rpId = "apple.com"
        // Allowed creds is sent as a comma separated string
        let validCreds = fsaChallenge.allowedCredentials.split(separator: ",").map(String.init)

        Task {
            do {
                let response = try fido2.respondToChallenge(args: ChallengeArgs(rpId: rpId, validCredentials: validCreds, devPin: pinCode, challenge: challenge, origin: origin))
            
                Task { @MainActor in
                    self.isProcessingAuthRequest = true
                }
                
                let respData = try JSONEncoder().encode(response)
                client.submitChallenge(response: respData, sessionData: AppleSessionData(serviceKey: sessionData.serviceKey, sessionID: sessionData.sessionID, scnt: sessionData.scnt))
                    .receive(on: DispatchQueue.main)
                    .handleEvents(
                        receiveOutput: { authenticationState in
                            self.authenticationState = authenticationState
                        },
                        receiveCompletion: { completion in
                            self.handleAuthenticationFlowCompletion(completion)
                            self.isProcessingAuthRequest = false
                        }
                    ).sink(
                        receiveCompletion: { _ in },
                        receiveValue: { _ in }
                    ).store(in: &cancellables)
            } catch FIDO2Error.canceledByUser {
                // User cancelled the auth flow
                // we don't have to show an error
                // because the sheet will already be dismissed
            } catch {
                Task { @MainActor in
                    authError = error
                }
            }
        }
    }

    func fido2DeviceIsPresent() -> Bool {
        fido2.hasDeviceAttached()
    }

    func fido2DeviceNeedsPin() -> Bool {
        do {
            return try fido2.deviceHasPin()
        } catch {
            Task { @MainActor in
                authError = error
            }

            return true
        }
    }
    
    func cancelSecurityKeyAssertationRequest() {
        self.fido2.cancel()
    }
    
    private func handleAuthenticationFlowCompletion(_ completion: Subscribers.Completion<Error>) {
        switch completion {
        case let .failure(error):
            // remove saved username and any stored keychain password if authentication fails so it doesn't try again.
            clearLoginCredentials()
            Logger.appState.error("Authentication error: \(error.legibleDescription)")
            self.authError = error
        case .finished:
            switch self.authenticationState {
                case .authenticated, .unauthenticated, .notAppleDeveloper:
                self.presentedSheet = nil
            case let .waitingForSecondFactor(option, authOptions, sessionData):
                self.handleTwoFactorOption(option, authOptions: authOptions, serviceKey: sessionData.serviceKey, sessionID: sessionData.sessionID, scnt: sessionData.scnt)
            }
        }
    }
    
    func signOut() {
        clearLoginCredentials()
        AppleAPI.Current.network.session.configuration.httpCookieStorage?.removeCookies(since: .distantPast)
        authenticationState = .unauthenticated
    }
    
    // MARK: - Helper
    
    /// Install the privileged helper if it isn't already installed.
    ///
    /// The way this is done is a little roundabout, because it requires user interaction in an alert before installation should be attempted.
    /// The first time this method is invoked should be with `shouldPrepareUserForHelperInstallation` set to true.
    /// If the helper is already installed, then nothing will happen.
    /// If the helper is not already installed, the user will be prepared for installation and this method will return early.
    /// If they consent to installing the helper then this method will be invoked again with  `shouldPrepareUserForHelperInstallation` set to false.
    /// This will install the helper.
    ///
    /// - Parameter shouldPrepareUserForHelperInstallation: Whether the user should be presented with an alert preparing them for helper installation.
    func installHelperIfNecessary(shouldPrepareUserForHelperInstallation: Bool = true) {
        guard helperInstallState == .installed || shouldPrepareUserForHelperInstallation == false else {
            isPreparingUserForActionRequiringHelper = { [unowned self] userConsented in
                guard userConsented else { return }
                self.installHelperIfNecessary(shouldPrepareUserForHelperInstallation: false) 
            }
            presentedAlert = .privilegedHelper
            return
        }
        
        installHelperIfNecessary()
            .sink(
                receiveCompletion: { [unowned self] completion in
                    if case let .failure(error) = completion {
                        self.error = error
                        self.presentedAlert = .generic(title: localizeString("Alert.PrivilegedHelper.Error.Title"), message: error.legibleLocalizedDescription)
                    }
                }, 
                receiveValue: {}
            )
            .store(in: &cancellables)
    }
    
    func installHelperIfNecessary() -> AnyPublisher<Void, Error> {
        Result {
            if helperInstallState == .notInstalled {
                try Current.helper.install()
                checkIfHelperIsInstalled()
            }
        }
        .publisher
        .subscribe(on: DispatchQueue.main)
        .eraseToAnyPublisher()
    }
    
    private func checkIfHelperIsInstalled() {
        helperInstallState = .unknown

        Current.helper.checkIfLatestHelperIsInstalled()
            .receive(on: DispatchQueue.main)
            .sink(
                receiveValue: { installed in
                    self.helperInstallState = installed ? .installed : .notInstalled
                }
            )
            .store(in: &cancellables)
    }
    
    // MARK: - Install
    
    func checkMinVersionAndInstall(id: XcodeID) {
        guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
        
        // Check to see if users macOS is supported
        if let requiredMacOSVersion = availableXcode.requiredMacOSVersion {
            if hasMinSupportedOS(requiredMacOSVersion: requiredMacOSVersion) {
                // prompt
                self.presentedAlert = .checkMinSupportedVersion(xcode: availableXcode, macOS: ProcessInfo.processInfo.operatingSystemVersion.versionString())
                return
            }
        }
        
        switch self.dataSource {
        case .apple:
            install(id: id)
        case .xcodeReleases:
            install(id: id)
        }
    }
    
    func hasMinSupportedOS(requiredMacOSVersion: String) -> Bool {
        let split = requiredMacOSVersion.components(separatedBy: ".").compactMap { Int($0) }
        let xcodeMinimumMacOSVersion = OperatingSystemVersion(majorVersion: split[safe: 0] ?? 0, minorVersion: split[safe: 1] ?? 0, patchVersion: split[safe: 2] ?? 0)
        
        return !ProcessInfo.processInfo.isOperatingSystemAtLeast(xcodeMinimumMacOSVersion)
    }
    
    func install(id: XcodeID) {
        guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }

        installationPublishers[id] = signInIfNeeded()
            .handleEvents(
                receiveSubscription: { [unowned self] _ in
                    self.setInstallationStep(of: availableXcode.version, to: .authenticating)
                }
            )
            .flatMap { [unowned self] in
                // signInIfNeeded might finish before the user actually authenticates if UI is involved. 
                // This publisher will wait for the @Published authentication state to change to authenticated or unauthenticated before finishing,
                // indicating that the user finished what they were doing in the UI.
                self.$authenticationState
                    .filter { state in
                        switch state {
                            case .authenticated, .unauthenticated, .notAppleDeveloper: return true
                        case .waitingForSecondFactor: return false
                        }
                    }
                    .prefix(1)
                    .tryMap { state in
                        if state == .unauthenticated {
                            throw AuthenticationError.invalidSession
                        }
                        if state == .notAppleDeveloper {
                            throw AuthenticationError.notDeveloperAppleId
                        }
                        return Void()
                    }
            }
            .flatMap {
                // This request would've already been made if the Apple data source were being used.
                // That's not the case for the Xcode Releases data source.
                // We need the cookies from its response in order to download Xcodes though,
                // so perform it here first just to be sure.
                Current.network.dataTask(with: URLRequest.downloads)
                    .map(\.data)
                    .decode(type: Downloads.self, decoder: configure(JSONDecoder()) {
                        $0.dateDecodingStrategy = .formatted(.downloadsDateModified)
                    })
                    .tryMap { downloads -> Void in
                        if downloads.hasError {
                            throw AuthenticationError.invalidResult(resultString: downloads.resultsString)
                        }
                       if downloads.downloads == nil {
                            throw AuthenticationError.invalidResult(resultString: localizeString("DownloadingError"))
                        }
                    }
                    .mapError { $0 as Error }
            }
            .flatMap { [unowned self] in
                self.install(.version(availableXcode), downloader: Downloader(rawValue: Current.defaults.string(forKey: "downloader") ?? "aria2") ?? .aria2)
            }
            .receive(on: DispatchQueue.main)
            .sink(
                receiveCompletion: { [unowned self] completion in 
                    self.installationPublishers[id] = nil
                    if case let .failure(error) = completion {
                        // Prevent setting the app state error if it is an invalid session, we will present the sign in view instead
                        if let error = error as? AuthenticationError, case .notAuthorized = error {
                            self.error = error
                            self.presentedAlert = .unauthenticated
                            
                        } else if error as? AuthenticationError != .invalidSession {
                            self.error = error
                            self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription)
                        }
                        if let index = self.allXcodes.firstIndex(where: { $0.id == id }) { 
                            self.allXcodes[index].installState = .notInstalled
                        }
                    }
                },
                receiveValue: { _ in }
            )
    }
    
    /// Skips using the username/password to log in to Apple, and simply gets a Auth Cookie used in downloading
    /// As of Nov 2022 this was returning a 403 forbidden
    func installWithoutLogin(id: Xcode.ID) {
        guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
        
        installationPublishers[id] = self.install(.version(availableXcode), downloader: Downloader(rawValue: Current.defaults.string(forKey: "downloader") ?? "aria2") ?? .aria2)
            .receive(on: DispatchQueue.main)
            .sink(
                receiveCompletion: { [unowned self] completion in
                    self.installationPublishers[id] = nil
                    if case let .failure(error) = completion {
                        // Prevent setting the app state error if it is an invalid session, we will present the sign in view instead
                        if error as? AuthenticationError != .invalidSession {
                            self.error = error
                            self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription)
                        }
                        if let index = self.allXcodes.firstIndex(where: { $0.id == id }) {
                            self.allXcodes[index].installState = .notInstalled
                        }
                    }
                },
                receiveValue: { _ in }
            )
    }
    
    func cancelInstall(id: Xcode.ID) {
        guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }

        // Cancel the publisher
        installationPublishers[id] = nil
        
        resetDockProgressTracking()
                
        // If the download is cancelled by the user, clean up the download files that aria2 creates.
        // This isn't done as part of the publisher with handleEvents(receiveCancel:) because it shouldn't happen when e.g. the app quits.
        let expectedArchivePath = Path.xcodesApplicationSupport/"Xcode-\(availableXcode.version).\(availableXcode.filename.suffix(fromLast: "."))"
        let aria2DownloadMetadataPath = expectedArchivePath.parent/(expectedArchivePath.basename() + ".aria2")
        try? Current.files.removeItem(at: expectedArchivePath.url)
        try? Current.files.removeItem(at: aria2DownloadMetadataPath.url)
        
        if let index = allXcodes.firstIndex(where: { $0.id == id }) { 
            allXcodes[index].installState = .notInstalled
        }
    }
    
    // MARK: - Uninstall
    func uninstall(xcode: Xcode) {
        guard
            let installedXcodePath = xcode.installedPath,
            uninstallPublisher == nil
        else { return }
        
        uninstallPublisher = uninstallXcode(path: installedXcodePath)
            .flatMap { [unowned self] _ in
                self.updateSelectedXcodePath()
            }
            .sink(
                receiveCompletion: { [unowned self] completion in
                    if case let .failure(error) = completion {
                        self.error = error
                        self.presentedAlert = .generic(title: localizeString("Alert.Uninstall.Error.Title"), message: error.legibleLocalizedDescription)
                    }
                    self.uninstallPublisher = nil
                },
                receiveValue: { _ in }
        )
    }
    
    func reveal(_ path: Path?) {
        // TODO: show error if not
        guard let path = path else { return }
        NSWorkspace.shared.activateFileViewerSelecting([path.url])
    }
    
    func reveal(path: String) {
        let url = URL(fileURLWithPath: path)
        NSWorkspace.shared.activateFileViewerSelecting([url])
    }

    /// Make an Xcode active, a.k.a select it, in the `xcode-select` sense.
    ///
    /// The underlying work is done by the privileged helper, so we need to make sure that it's installed first.
    /// The way this is done is a little roundabout, because it requires user interaction in an alert before the `selectPublisher` is subscribed to.
    /// The first time this method is invoked should be with `shouldPrepareUserForHelperInstallation` set to true.
    /// If the helper is already installed, the Xcode will be made active immediately.
    /// If the helper is not already installed, the user will be prepared for installation and this method will return early.
    /// If they consent to installing the helper then this method will be invoked again with  `shouldPrepareUserForHelperInstallation` set to false.
    /// This will install the helper and make the Xcode active.
    ///
    /// - Parameter xcode: The Xcode to make active.
    /// - Parameter shouldPrepareUserForHelperInstallation: Whether the user should be presented with an alert preparing them for helper installation before making the Xcode version active.
    func select(xcode: Xcode, shouldPrepareUserForHelperInstallation: Bool = true) {
        guard helperInstallState == .installed || shouldPrepareUserForHelperInstallation == false else {
            isPreparingUserForActionRequiringHelper = { [unowned self] userConsented in
                guard userConsented else { return }
                self.select(xcode: xcode, shouldPrepareUserForHelperInstallation: false)
            }
            presentedAlert = .privilegedHelper
            return
        }

        guard
            var installedXcodePath = xcode.installedPath,
            selectPublisher == nil
        else { return }
       
        if onSelectActionType == .rename {
            guard let newDestinationXcodePath = renameToXcode(xcode: xcode) else { return }
            installedXcodePath = newDestinationXcodePath
        }
        
        selectPublisher = installHelperIfNecessary()
            .flatMap {
                Current.helper.switchXcodePath(installedXcodePath.string)
            }
            .flatMap { [unowned self] _ in
                self.updateSelectedXcodePath()
            }
            .sink(
                receiveCompletion: { [unowned self] completion in
                    if case let .failure(error) = completion {
                        self.error = error
                        self.presentedAlert = .generic(title: localizeString("Alert.Select.Error.Title"), message: error.legibleLocalizedDescription)
                    } else {
                        if self.createSymLinkOnSelect {
                            createSymbolicLink(xcode: xcode)
                        }
                    }
                    self.selectPublisher = nil
                },
                receiveValue: { _ in }
            )
    }
    
    func open(xcode: Xcode, openInRosetta: Bool? = false) {
        switch xcode.installState {
        case let .installed(path):
            let config = NSWorkspace.OpenConfiguration.init()
            if (openInRosetta ?? false) {
                config.architecture = CPU_TYPE_X86_64
            }
            config.allowsRunningApplicationSubstitution = false
            NSWorkspace.shared.openApplication(at: path.url, configuration: config)
        default:
            Logger.appState.error("\(xcode.id.version) is not installed")
            return
        }
    }
    
    func copyPath(xcode: Xcode) {
        guard let installedXcodePath = xcode.installedPath else { return }
        
        NSPasteboard.general.declareTypes([.URL, .string], owner: nil)
        NSPasteboard.general.writeObjects([installedXcodePath.url as NSURL])
        NSPasteboard.general.setString(installedXcodePath.string, forType: .string)
    }

    func copyReleaseNote(from url: URL?) {
      guard let url = url else { return }
      NSPasteboard.general.declareTypes([.URL, .string], owner: nil)
      NSPasteboard.general.writeObjects([url as NSURL])
      NSPasteboard.general.setString(url.absoluteString, forType: .string)
    }
    
    func createSymbolicLink(xcode: Xcode, isBeta: Bool = false) {
        guard let installedXcodePath = xcode.installedPath else { return }
        
        let destinationPath: Path = Path.installDirectory/"Xcode\(isBeta ? "-Beta" : "").app"
        
        // does an Xcode.app file exist?
        if FileManager.default.fileExists(atPath: destinationPath.string) {
            do {
                // if it's not a symlink, error because we don't want to delete an actual xcode.app file
                let attributes: [FileAttributeKey : Any]? = try? FileManager.default.attributesOfItem(atPath: destinationPath.string)
                
                if attributes?[.type] as? FileAttributeType == FileAttributeType.typeSymbolicLink {
                    try FileManager.default.removeItem(atPath: destinationPath.string)
                    Logger.appState.info("Successfully deleted old symlink")
                } else {
                    self.presentedAlert = .generic(title: localizeString("Alert.SymLink.Title"), message: localizeString("Alert.SymLink.Message"))
                    return
                }
            } catch {
                self.presentedAlert = .generic(title: localizeString("Alert.SymLink.Title"), message: error.localizedDescription)
            }
        }
        
        do {
            try FileManager.default.createSymbolicLink(atPath: destinationPath.string, withDestinationPath: installedXcodePath.string)
            Logger.appState.info("Successfully created symbolic link with Xcode\(isBeta ? "-Beta": "").app")
        } catch {
            Logger.appState.error("Unable to create symbolic Link")
            self.error = error
            self.presentedAlert = .generic(title: localizeString("Alert.SymLink.Title"), message: error.legibleLocalizedDescription)
        }
    }
    
    func renameToXcode(xcode: Xcode) -> Path? {
        guard let installedXcodePath = xcode.installedPath else { return nil }
        
        let destinationPath: Path = Path.installDirectory/"Xcode.app"
        
        // rename any old named `Xcode.app` to the Xcodes versioned named files
        if FileManager.default.fileExists(atPath: destinationPath.string) {
            if let originalXcode = Current.files.installedXcode(destination: destinationPath) {
                let newName = "Xcode-\(originalXcode.version.descriptionWithoutBuildMetadata).app"
                Logger.appState.debug("Found Xcode.app - renaming back to \(newName)")
                do {
                    try destinationPath.rename(to: newName)
                } catch {
                    Logger.appState.error("Unable to create rename Xcode.app back to original")
                    self.error = error
                    // TODO UPDATE MY ERROR STRING
                    self.presentedAlert = .generic(title: localizeString("Alert.SymLink.Title"), message: error.legibleLocalizedDescription)
                }
            }
        }
        // rename passed in xcode to xcode.app
        Logger.appState.debug("Found Xcode.app - renaming back to Xcode.app")
        do {
            return try installedXcodePath.rename(to: "Xcode.app")
        } catch {
            Logger.appState.error("Unable to create rename Xcode.app back to original")
            self.error = error
            // TODO UPDATE MY ERROR STRING
            self.presentedAlert = .generic(title: localizeString("Alert.SymLink.Title"), message: error.legibleLocalizedDescription)
        }
        return nil
    }

    func updateAllXcodes(availableXcodes: [AvailableXcode], installedXcodes: [InstalledXcode], selectedXcodePath: String?) {
        var adjustedAvailableXcodes = availableXcodes
        
        // First, adjust all of the available Xcodes so that available and installed versions line up and the second part of this function works properly.
        if dataSource == .apple {
            for installedXcode in installedXcodes {
                // We can trust that build metadata identifiers are unique for each version of Xcode, so if we have it then it's all we need.
                // If build metadata matches exactly, replace the available version with the installed version.
                // This should handle Apple versions from /downloads/more which don't have build metadata identifiers. 
                if let index = adjustedAvailableXcodes.map(\.version).firstIndex(where: { $0.buildMetadataIdentifiers == installedXcode.version.buildMetadataIdentifiers }) {
                    adjustedAvailableXcodes[index].xcodeID = installedXcode.xcodeID
                }
                // If an installed version is the same as one that's listed online which doesn't have build metadata, replace it with the installed version
                // Not all prerelease Apple versions available online include build metadata
                else if let index = adjustedAvailableXcodes.firstIndex(where: { availableXcode in
                    availableXcode.version.isEquivalent(to: installedXcode.version) &&
                        availableXcode.version.buildMetadataIdentifiers.isEmpty
                }) {
                    adjustedAvailableXcodes[index].xcodeID = installedXcode.xcodeID
                }
            }
        }

        // Map all of the available versions into Xcode values that join available and installed Xcode data for display.
        var newAllXcodes = adjustedAvailableXcodes
            .filter { availableXcode in
                // If we don't have the build identifier, don't attempt to filter prerelease versions with identical build identifiers
                guard !availableXcode.version.buildMetadataIdentifiers.isEmpty else { return true }

                let availableXcodesWithIdenticalBuildIdentifiers = availableXcodes
                    .filter({ $0.version.buildMetadataIdentifiers == availableXcode.version.buildMetadataIdentifiers })
                
                // Include this version if there's only one with this build identifier
                return availableXcodesWithIdenticalBuildIdentifiers.count == 1 ||
                    // Or if there's more than one with this build identifier and this is the release version
                
                availableXcodesWithIdenticalBuildIdentifiers.count > 1 && (availableXcode.version.prereleaseIdentifiers.isEmpty || availableXcode.architectures?.count ?? 0 != 0)
            }
            .map { availableXcode -> Xcode in
                let installedXcode = installedXcodes.first(where: { installedXcode in
                    // if we want to have only specific Xcodes as selected instead of the Architecture Equivalent. 
                   // if availableXcode.architectures == nil {
//                        return availableXcode.version.isEquivalent(to: installedXcode.version)
//                    } else {
//                        return availableXcode.xcodeID == installedXcode.xcodeID
//                    }
                    return availableXcode.version.isEquivalent(to: installedXcode.version)
                })

                let identicalBuilds: [XcodeID]
                let prereleaseAvailableXcodesWithIdenticalBuildIdentifiers = availableXcodes
                    .filter {
                        return $0.version.buildMetadataIdentifiers == availableXcode.version.buildMetadataIdentifiers &&
                            !$0.version.prereleaseIdentifiers.isEmpty &&
                            // If we don't have the build identifier, don't consider this as a potential identical build
                            !$0.version.buildMetadataIdentifiers.isEmpty
                    }
                // If this is the release version, add the identical builds to it
                if !prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.isEmpty, availableXcode.version.prereleaseIdentifiers.isEmpty {
                    identicalBuilds = [availableXcode.xcodeID] + prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.map(\.xcodeID)
                } else {
                    identicalBuilds = []
                }
                
                // If the existing install state is "installing", keep it 
                let existingXcodeInstallState = allXcodes.first { $0.id == availableXcode.xcodeID && $0.installState.installing }?.installState
                // Otherwise, determine it from whether there's an installed Xcode
             
Download .txt
gitextract_lwe5ux5j/

├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── dependabot.yml
│   ├── release-drafter.yml
│   └── workflows/
│       ├── appcast.yml
│       ├── ci.yml
│       ├── release-drafter.yml
│       └── xcstrings.yml
├── .gitignore
├── AppCast/
│   ├── .gitignore
│   ├── Gemfile
│   ├── _config.yml
│   ├── _includes/
│   │   └── appcast.inc
│   ├── _plugins/
│   │   └── signature_filter.rb
│   ├── appcast.xml
│   └── appcast_pre.xml
├── CONTRIBUTING.md
├── DECISIONS.md
├── HelperXPCShared/
│   └── HelperXPCShared.swift
├── LICENSE
├── README.md
├── Scripts/
│   ├── export_options.plist
│   ├── fix_libfido2_framework.sh
│   ├── increment_build_number.sh
│   ├── notarize.sh
│   ├── package_release.sh
│   ├── sign_update
│   └── uninstall_privileged_helper.sh
├── Xcodes/
│   ├── AcknowledgementsGenerator/
│   │   ├── .gitignore
│   │   ├── .swiftpm/
│   │   │   └── xcode/
│   │   │       └── package.xcworkspace/
│   │   │           └── contents.xcworkspacedata
│   │   ├── Package.swift
│   │   ├── README.md
│   │   ├── Sources/
│   │   │   └── AcknowledgementsGenerator/
│   │   │       ├── Extensions/
│   │   │       │   ├── CollectionExtensions.swift
│   │   │       │   └── StringExtensions.swift
│   │   │       ├── Tools/
│   │   │       │   └── Xcode.swift
│   │   │       └── main.swift
│   │   └── spm-licenses.LICENSE
│   ├── AppleAPI/
│   │   ├── .gitignore
│   │   ├── Package.swift
│   │   ├── README.md
│   │   ├── Sources/
│   │   │   └── AppleAPI/
│   │   │       ├── Client.swift
│   │   │       ├── Environment.swift
│   │   │       ├── Hashcash.swift
│   │   │       └── URLRequest+Apple.swift
│   │   └── Tests/
│   │       ├── AppleAPITests/
│   │       │   ├── AppleAPITests.swift
│   │       │   └── XCTestManifests.swift
│   │       └── LinuxMain.swift
│   ├── Backend/
│   │   ├── AppState+Install.swift
│   │   ├── AppState+Runtimes.swift
│   │   ├── AppState+Update.swift
│   │   ├── AppState.swift
│   │   ├── Aria2CError.swift
│   │   ├── AvailableXcode.swift
│   │   ├── Bundle+InfoPlistValues.swift
│   │   ├── Collection+.swift
│   │   ├── Configure.swift
│   │   ├── DataSource.swift
│   │   ├── DateFormatter+.swift
│   │   ├── Downloader.swift
│   │   ├── Downloads.swift
│   │   ├── Entry+.swift
│   │   ├── Environment.swift
│   │   ├── FileError.swift
│   │   ├── FileManager+.swift
│   │   ├── FocusedValues.swift
│   │   ├── Foundation.swift
│   │   ├── Hardware.swift
│   │   ├── HelperClient.swift
│   │   ├── HelperInstallState.swift
│   │   ├── InstalledXcode.swift
│   │   ├── IsTesting.swift
│   │   ├── NotificationManager.swift
│   │   ├── Optional+IsNotNil.swift
│   │   ├── Path+.swift
│   │   ├── Process.swift
│   │   ├── Progress+.swift
│   │   ├── Publisher+Resumable.swift
│   │   ├── SDKs+Xcode.swift
│   │   ├── SelectedActionType.swift
│   │   ├── SelectedXcode.swift
│   │   ├── URLRequest+Apple.swift
│   │   ├── URLSession+DownloadTaskPublisher.swift
│   │   ├── Version+.swift
│   │   ├── Version+Xcode.swift
│   │   ├── Version+XcodeReleases.swift
│   │   ├── Xcode.swift
│   │   ├── XcodeCommands.swift
│   │   └── XcodeInstallState.swift
│   ├── Frontend/
│   │   ├── About/
│   │   │   ├── AboutView.swift
│   │   │   ├── AcknowledgementsView.swift
│   │   │   └── ScrollingTextView.swift
│   │   ├── Common/
│   │   │   ├── NavigationSplitViewWrapper.swift
│   │   │   ├── ObservingProgressIndicator.swift
│   │   │   ├── ProgressButton.swift
│   │   │   ├── ProgressIndicator.swift
│   │   │   ├── TagView.swift
│   │   │   ├── TrailingIconLabelStyle.swift
│   │   │   ├── XcodesAlert.swift
│   │   │   └── XcodesSheet.swift
│   │   ├── InfoPane/
│   │   │   ├── CompatibilityView.swift
│   │   │   ├── CompilersView.swift
│   │   │   ├── CornerRadiusModifier.swift
│   │   │   ├── IconView.swift
│   │   │   ├── IdenticalBuildView.swift
│   │   │   ├── InfoPane.swift
│   │   │   ├── InfoPaneControls.swift
│   │   │   ├── InstallationStepDetailView.swift
│   │   │   ├── InstalledStateButtons.swift
│   │   │   ├── NotInstalledStateButtons.swift
│   │   │   ├── PlatformsView.swift
│   │   │   ├── ReleaseDateView.swift
│   │   │   ├── ReleaseNotesView.swift
│   │   │   ├── RuntimeInstallationStepDetailView.swift
│   │   │   ├── SDKsView.swift
│   │   │   └── UnselectedView.swift
│   │   ├── MainWindow.swift
│   │   ├── Preferences/
│   │   │   ├── AdvancedPreferencePane.swift
│   │   │   ├── DownloadPreferencePane.swift
│   │   │   ├── ExperiementsPreferencePane.swift
│   │   │   ├── GeneralPreferencePane.swift
│   │   │   ├── NotificationsView.swift
│   │   │   ├── PlatformsListView.swift
│   │   │   ├── PreferencesView.swift
│   │   │   └── UpdatesPreferencePane.swift
│   │   ├── SignIn/
│   │   │   ├── AttributedText.swift
│   │   │   ├── NSAttributedString+.swift
│   │   │   ├── PinCodeTextView.swift
│   │   │   ├── SignIn2FAView.swift
│   │   │   ├── SignInCredentialsView.swift
│   │   │   ├── SignInPhoneListView.swift
│   │   │   ├── SignInSMSView.swift
│   │   │   ├── SignInSecurityKeyPinView.swift
│   │   │   ├── SignInSecurityKeyTouchView.swift
│   │   │   └── SignedInView.swift
│   │   ├── View+Conditional.swift
│   │   ├── View+IsHidden.swift
│   │   └── XcodeList/
│   │       ├── AppStoreButtonStyle.swift
│   │       ├── BottomStatusBar.swift
│   │       ├── InstallationStepRowView.swift
│   │       ├── MainToolbar.swift
│   │       ├── Tag.swift
│   │       ├── XcodeListCategory.swift
│   │       ├── XcodeListView.swift
│   │       └── XcodeListViewRow.swift
│   ├── Preview Content/
│   │   └── Preview Assets.xcassets/
│   │       └── Contents.json
│   ├── Resources/
│   │   ├── Assets.xcassets/
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   ├── Contents.json
│   │   │   ├── Icons/
│   │   │   │   └── Contents.json
│   │   │   ├── install.imageset/
│   │   │   │   └── Contents.json
│   │   │   ├── xcode-beta.imageset/
│   │   │   │   └── Contents.json
│   │   │   └── xcode.imageset/
│   │   │       └── Contents.json
│   │   ├── Info.plist
│   │   ├── Licenses.rtf
│   │   ├── Localizable.xcstrings
│   │   ├── Xcodes.entitlements
│   │   ├── XcodesIcon.icon/
│   │   │   └── icon.json
│   │   ├── XcodesTest.entitlements
│   │   ├── aria2c
│   │   ├── aria2c.LICENSE
│   │   └── unxip
│   ├── XcodesApp.swift
│   └── XcodesKit/
│       ├── .gitignore
│       ├── Package.swift
│       ├── README.md
│       ├── Sources/
│       │   └── XcodesKit/
│       │       ├── Extensions/
│       │       │   ├── Foundation.swift
│       │       │   └── Logger.swift
│       │       ├── Models/
│       │       │   ├── Runtimes/
│       │       │   │   ├── CoreSimulatorImage.swift
│       │       │   │   ├── RuntimeInstallState.swift
│       │       │   │   ├── RuntimeInstallationStep.swift
│       │       │   │   └── Runtimes.swift
│       │       │   ├── XcodeInstallState.swift
│       │       │   ├── XcodeInstallationStep.swift
│       │       │   └── XcodeReleases/
│       │       │       ├── Architecture.swift
│       │       │       ├── Checksums.swift
│       │       │       ├── Compilers.swift
│       │       │       ├── Link.swift
│       │       │       ├── Release.swift
│       │       │       ├── SDKs.swift
│       │       │       ├── XcodeRelease.swift
│       │       │       ├── XcodeVersion.swift
│       │       │       └── YMD.swift
│       │       ├── Services/
│       │       │   └── RuntimeService.swift
│       │       ├── Shell/
│       │       │   ├── Process.swift
│       │       │   └── XcodesShell.swift
│       │       └── XcodesKitEnvironment.swift
│       └── Tests/
│           └── XcodesKitTests/
│               └── XcodesKitTests.swift
├── Xcodes.xcodeproj/
│   ├── ._project.xcworkspace
│   ├── project.pbxproj
│   ├── project.xcworkspace/
│   │   ├── contents.xcworkspacedata
│   │   └── xcshareddata/
│   │       ├── IDEWorkspaceChecks.plist
│   │       └── swiftpm/
│   │           └── Package.resolved
│   └── xcshareddata/
│       └── xcschemes/
│           ├── Xcodes.xcscheme
│           └── com.robotsandpencils.XcodesApp.Helper.xcscheme
├── XcodesTests/
│   ├── AppStateTests.swift
│   ├── AppStateUpdateTests.swift
│   ├── Bundle+XcodesTests.swift
│   ├── Environment+Mock.swift
│   ├── Fixtures/
│   │   ├── Stub-0.0.0.Info.plist
│   │   └── Stub-version.plist
│   └── Info.plist
├── com.xcodesorg.xcodesapp.Helper/
│   ├── AuditTokenHack.h
│   ├── AuditTokenHack.m
│   ├── ConnectionVerifier.swift
│   ├── Info.plist
│   ├── Logger.swift
│   ├── SimpleXPCApp.LICENSE
│   ├── XPCDelegate.swift
│   ├── com.xcodesorg.xcodesapp.Helper-Bridging-Header.h
│   ├── com.xcodesorg.xcodesapp.HelperTest.entitlements
│   ├── launchd.plist
│   └── main.swift
└── nextstep.md
Download .txt
SYMBOL INDEX (3 symbols across 1 files)

FILE: AppCast/_plugins/signature_filter.rb
  type Jekyll (line 1) | module Jekyll
    type SignatureFilter (line 2) | module SignatureFilter
      function sparkle_signature (line 3) | def sparkle_signature(release_body)
Condensed preview — 213 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,554K chars).
[
  {
    "path": ".github/CODEOWNERS",
    "chars": 29,
    "preview": "*   @RobotsAndPencils/xcodes\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 426,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 286,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**Te"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 162,
    "preview": "version: 2\nupdates:\n  # Maintain dependencies for GitHub Actions\n  - package-ecosystem: \"github-actions\"\n    directory: "
  },
  {
    "path": ".github/release-drafter.yml",
    "chars": 533,
    "preview": "categories:\n  - title: '🚀 Enhancements'\n    labels:\n      - 'enhancement'\n  - title: '🐛 Bug Fixes'\n    labels:\n      - '"
  },
  {
    "path": ".github/workflows/appcast.yml",
    "chars": 1312,
    "preview": "name: Build and publish a new appcast file\n\non:\n  workflow_dispatch:\n  release:\n\njobs:\n  jekyll:\n    runs-on: ubuntu-lat"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 272,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  test:\n    runs-on: macos-15\n    steps:\n    - u"
  },
  {
    "path": ".github/workflows/release-drafter.yml",
    "chars": 343,
    "preview": "name: Release Drafter\n\non:\n  # Allow running it manually in case we forget to label a PR before merging\n  workflow_dispa"
  },
  {
    "path": ".github/workflows/xcstrings.yml",
    "chars": 604,
    "preview": "name: XCStrings Validation\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  test:\n "
  },
  {
    "path": ".gitignore",
    "chars": 2226,
    "preview": "# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n"
  },
  {
    "path": "AppCast/.gitignore",
    "chars": 35,
    "preview": "_site\n.sass-cache\n.jekyll-metadata\n"
  },
  {
    "path": "AppCast/Gemfile",
    "chars": 900,
    "preview": "source \"https://rubygems.org\"\n\n# Hello! This is where you manage which Jekyll version is used to run.\n# When you want to"
  },
  {
    "path": "AppCast/_config.yml",
    "chars": 1354,
    "preview": "# Welcome to Jekyll!\n#\n# This config file is meant for settings that affect your whole blog, values\n# which you are expe"
  },
  {
    "path": "AppCast/_includes/appcast.inc",
    "chars": 2485,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rss version=\"2.0\" xmlns:sparkle=\"http://www.andymatuschak.org/xml-namespaces/spa"
  },
  {
    "path": "AppCast/_plugins/signature_filter.rb",
    "chars": 385,
    "preview": "module Jekyll\n  module SignatureFilter\n    def sparkle_signature(release_body)\n      regex = /<!-- sparkle:edSignature=("
  },
  {
    "path": "AppCast/appcast.xml",
    "chars": 52,
    "preview": "---\nrelease_only: true\n---\n{%include appcast.inc %}\n"
  },
  {
    "path": "AppCast/appcast_pre.xml",
    "chars": 53,
    "preview": "---\nrelease_only: false\n---\n{%include appcast.inc %}\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1783,
    "preview": "# Contributing to Xcodes\nWe love your input! We want to make contributing to this project as easy and transparent as pos"
  },
  {
    "path": "DECISIONS.md",
    "chars": 9939,
    "preview": "# Decisions\n\nThis file exists to provide a historical record of the motivation for important technical decisions in the "
  },
  {
    "path": "HelperXPCShared/HelperXPCShared.swift",
    "chars": 769,
    "preview": "import Foundation\n\nlet machServiceName = \"com.xcodesorg.xcodesapp.Helper\"\nlet clientBundleID = \"com.xcodesorg.xcodesapp\""
  },
  {
    "path": "LICENSE",
    "chars": 1080,
    "preview": "MIT License\n\nCopyright (c) 2019-2021 Robots and Pencils\n\nPermission is hereby granted, free of charge, to any person obt"
  },
  {
    "path": "README.md",
    "chars": 8261,
    "preview": "<h1><img src=\"icon.png\" align=\"center\" width=50 height=50 /> <img src=\"IconDark.png\" align=\"center\" width=50 height=50 /"
  },
  {
    "path": "Scripts/export_options.plist",
    "chars": 238,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Scripts/fix_libfido2_framework.sh",
    "chars": 1097,
    "preview": "#!/bin/sh\n\n# Fix libfido2.framework structure for macOS validation\n# If this script is not run, the build will fail beca"
  },
  {
    "path": "Scripts/increment_build_number.sh",
    "chars": 853,
    "preview": "#!/bin/sh\n#\n# Increment build number\n#\n# This will get the latest build number from git tags, add 1, then set it in the "
  },
  {
    "path": "Scripts/notarize.sh",
    "chars": 2043,
    "preview": "#!/bin/sh\n#\n# Notarize\n#\n# Uploads to Apple's notarization service, polls until it completes, staples the ticket to the "
  },
  {
    "path": "Scripts/package_release.sh",
    "chars": 1235,
    "preview": "#!/bin/bash\n#\n# Package release\n#\n# This will build and archive the app and then compress it in a .zip file at Product/X"
  },
  {
    "path": "Scripts/uninstall_privileged_helper.sh",
    "chars": 557,
    "preview": "#!/bin/bash\n\nPRIVILEGED_HELPER_LABEL=com.xcodesorg.xcodesapp.Helper\n\nsudo rm /Library/PrivilegedHelperTools/$PRIVILEGED_"
  },
  {
    "path": "Xcodes/AcknowledgementsGenerator/.gitignore",
    "chars": 53,
    "preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\n"
  },
  {
    "path": "Xcodes/AcknowledgementsGenerator/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata",
    "chars": 135,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef"
  },
  {
    "path": "Xcodes/AcknowledgementsGenerator/Package.swift",
    "chars": 465,
    "preview": "// swift-tools-version:5.4\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"AcknowledgementsGenerator\",\n   "
  },
  {
    "path": "Xcodes/AcknowledgementsGenerator/README.md",
    "chars": 191,
    "preview": "# AcknowledgementsGenerator\n\nScans an Xcode project's checked-out SPM packages for license files, then combines them int"
  },
  {
    "path": "Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/Extensions/CollectionExtensions.swift",
    "chars": 413,
    "preview": "//\n//  CollectionExtensions.swift\n//  spm-licenses\n//\n//  Created by Sergii Kryvoblotskyi on 11/11/19.\n//  Copyright © 2"
  },
  {
    "path": "Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/Extensions/StringExtensions.swift",
    "chars": 856,
    "preview": "//\n//  StringExtensions.swift\n//  spm-licenses\n//\n//  Created by Sergii Kryvoblotskyi on 11/11/19.\n//  Copyright © 2019 "
  },
  {
    "path": "Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/Tools/Xcode.swift",
    "chars": 2102,
    "preview": "//\n//  Xcode.swift\n//  spm-licenses\n//\n//  Created by Sergii Kryvoblotskyi on 11/11/19.\n//  Copyright © 2019 MacPaw. All"
  },
  {
    "path": "Xcodes/AcknowledgementsGenerator/Sources/AcknowledgementsGenerator/main.swift",
    "chars": 4478,
    "preview": "//\n//  main.swift\n//  spm-licenses\n//\n//  Created by Sergii Kryvoblotskyi on 11/11/19.\n//  Copyright © 2019 MacPaw. All "
  },
  {
    "path": "Xcodes/AcknowledgementsGenerator/spm-licenses.LICENSE",
    "chars": 1063,
    "preview": "MIT License\n\nCopyright (c) 2019 MacPaw\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
  },
  {
    "path": "Xcodes/AppleAPI/.gitignore",
    "chars": 53,
    "preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\n"
  },
  {
    "path": "Xcodes/AppleAPI/Package.swift",
    "chars": 1027,
    "preview": "// swift-tools-version:5.7\n// The swift-tools-version declares the minimum version of Swift required to build this packa"
  },
  {
    "path": "Xcodes/AppleAPI/README.md",
    "chars": 43,
    "preview": "# AppleAPI\n\nA description of this package.\n"
  },
  {
    "path": "Xcodes/AppleAPI/Sources/AppleAPI/Client.swift",
    "chars": 27614,
    "preview": "import Foundation\nimport Combine\nimport SRP\nimport Crypto\nimport CommonCrypto\n\n\npublic class Client {\n    private static"
  },
  {
    "path": "Xcodes/AppleAPI/Sources/AppleAPI/Environment.swift",
    "chars": 734,
    "preview": "import Foundation\nimport Combine\n\n/**\n Lightweight dependency injection using global mutable state :P\n\n - SeeAlso: https"
  },
  {
    "path": "Xcodes/AppleAPI/Sources/AppleAPI/Hashcash.swift",
    "chars": 3399,
    "preview": "//\n//  Hashcash.swift\n//  \n//\n//  Created by Matt Kiazyk on 2023-02-23.\n//\n\nimport Foundation\nimport CryptoKit\nimport Co"
  },
  {
    "path": "Xcodes/AppleAPI/Sources/AppleAPI/URLRequest+Apple.swift",
    "chars": 9447,
    "preview": "import Foundation\n\npublic extension URL {\n    static let itcServiceKey = URL(string: \"https://appstoreconnect.apple.com/"
  },
  {
    "path": "Xcodes/AppleAPI/Tests/AppleAPITests/AppleAPITests.swift",
    "chars": 931,
    "preview": "import XCTest\n@testable import AppleAPI\n\nfinal class AppleAPITests: XCTestCase {\n    \n    func testValidHashCashMint() {"
  },
  {
    "path": "Xcodes/AppleAPI/Tests/AppleAPITests/XCTestManifests.swift",
    "chars": 158,
    "preview": "import XCTest\n\n#if !canImport(ObjectiveC)\npublic func allTests() -> [XCTestCaseEntry] {\n    return [\n        testCase(Ap"
  },
  {
    "path": "Xcodes/AppleAPI/Tests/LinuxMain.swift",
    "chars": 118,
    "preview": "import XCTest\n\nimport AppleAPITests\n\nvar tests = [XCTestCaseEntry]()\ntests += AppleAPITests.allTests()\nXCTMain(tests)\n"
  },
  {
    "path": "Xcodes/Backend/AppState+Install.swift",
    "chars": 30260,
    "preview": "import Combine\nimport Foundation\nimport Path\nimport AppleAPI\nimport Version\nimport LegibleError\nimport os.log\nimport Doc"
  },
  {
    "path": "Xcodes/Backend/AppState+Runtimes.swift",
    "chars": 16091,
    "preview": "import Foundation\nimport XcodesKit\nimport OSLog\nimport Combine\nimport Path\nimport AppleAPI\nimport Version\n\nextension App"
  },
  {
    "path": "Xcodes/Backend/AppState+Update.swift",
    "chars": 11094,
    "preview": "import Combine\nimport Foundation\nimport Path\nimport Version\nimport SwiftSoup\nimport AppleAPI\nimport XcodesKit\n\nextension"
  },
  {
    "path": "Xcodes/Backend/AppState.swift",
    "chars": 45686,
    "preview": "import AppKit\nimport AppleAPI\nimport Combine\nimport Path\nimport LegibleError\nimport KeychainAccess\nimport Path\nimport Ve"
  },
  {
    "path": "Xcodes/Backend/Aria2CError.swift",
    "chars": 4655,
    "preview": "import Foundation\n\n/// A LocalizedError that represents a non-zero exit code from running aria2c.\nstruct Aria2CError: Lo"
  },
  {
    "path": "Xcodes/Backend/AvailableXcode.swift",
    "chars": 1402,
    "preview": "import Foundation\nimport Version\nimport XcodesKit\n\n/// A version of Xcode that's available for installation\npublic struc"
  },
  {
    "path": "Xcodes/Backend/Bundle+InfoPlistValues.swift",
    "chars": 443,
    "preview": "import Foundation\n\nextension Bundle {\n    var bundleName: String? {\n        infoDictionary?[\"CFBundleName\"] as? String\n "
  },
  {
    "path": "Xcodes/Backend/Collection+.swift",
    "chars": 403,
    "preview": "//\n//  Collection+.swift\n//  Xcodes\n//\n//  Created by Matt Kiazyk on 2022-04-11.\n//  Copyright © 2022 Robots and Pencils"
  },
  {
    "path": "Xcodes/Backend/Configure.swift",
    "chars": 170,
    "preview": "public func configure<Subject>(_ subject: Subject, configuration: (inout Subject) -> Void) -> Subject {\n    var copy = s"
  },
  {
    "path": "Xcodes/Backend/DataSource.swift",
    "chars": 482,
    "preview": "import Foundation\n\npublic enum DataSource: String, CaseIterable, Identifiable, CustomStringConvertible {\n    case apple\n"
  },
  {
    "path": "Xcodes/Backend/DateFormatter+.swift",
    "chars": 642,
    "preview": "import Foundation\n\nextension DateFormatter {\n    /// Date format used in JSON returned from `URL.downloads`\n    static l"
  },
  {
    "path": "Xcodes/Backend/Downloader.swift",
    "chars": 420,
    "preview": "import Foundation\nimport Path\n\npublic enum Downloader: String, CaseIterable, Identifiable, CustomStringConvertible {\n   "
  },
  {
    "path": "Xcodes/Backend/Downloads.swift",
    "chars": 558,
    "preview": "import Foundation\nimport Path\nimport Version\n\nstruct Downloads: Codable {\n    let resultCode: Int\n    let resultsString:"
  },
  {
    "path": "Xcodes/Backend/Entry+.swift",
    "chars": 713,
    "preview": "import Foundation\nimport Path\n\nextension Path {\n    static func isAppBundle(path: Path) -> Bool {\n        path.isDirecto"
  },
  {
    "path": "Xcodes/Backend/Environment.swift",
    "chars": 19908,
    "preview": "import Combine\nimport Foundation\nimport Path\nimport AppleAPI\nimport KeychainAccess\nimport XcodesKit\n/**\n Lightweight dep"
  },
  {
    "path": "Xcodes/Backend/FileError.swift",
    "chars": 503,
    "preview": "//\n//  FileError.swift\n//  Xcodes\n//\n//  Created by Leon Wolf on 06.10.22.\n//  Copyright © 2022 Robots and Pencils. All "
  },
  {
    "path": "Xcodes/Backend/FileManager+.swift",
    "chars": 711,
    "preview": "import Foundation\n\nextension FileManager {\n    /**\n     Moves an item to the trash.\n     \n     This implementation exist"
  },
  {
    "path": "Xcodes/Backend/FocusedValues.swift",
    "chars": 331,
    "preview": "import SwiftUI\n\n// MARK: - FocusedXcodeKey\n\nstruct FocusedXcodeKey : FocusedValueKey {\n    typealias Value = SelectedXco"
  },
  {
    "path": "Xcodes/Backend/Foundation.swift",
    "chars": 824,
    "preview": "import Foundation\n\npublic extension BidirectionalCollection where Element: Equatable {\n    func suffix(fromLast delimite"
  },
  {
    "path": "Xcodes/Backend/Hardware.swift",
    "chars": 946,
    "preview": "import Foundation\n\n\nstruct Hardware {\n    \n    ///\n    ///  Determines the architecture of the Mac on which we're runnin"
  },
  {
    "path": "Xcodes/Backend/HelperClient.swift",
    "chars": 15222,
    "preview": "import Combine\nimport Foundation\nimport os.log\nimport ServiceManagement\n\nfinal class HelperClient {\n    private var conn"
  },
  {
    "path": "Xcodes/Backend/HelperInstallState.swift",
    "chars": 123,
    "preview": "import Foundation\n\npublic enum HelperInstallState: Equatable {\n    case unknown\n    case notInstalled\n    case installed"
  },
  {
    "path": "Xcodes/Backend/InstalledXcode.swift",
    "chars": 3103,
    "preview": "import Foundation\nimport Version\nimport Path\nimport XcodesKit\n\n/// A version of Xcode that's already installed\npublic st"
  },
  {
    "path": "Xcodes/Backend/IsTesting.swift",
    "chars": 74,
    "preview": "import Foundation\n\nlet isTesting = NSClassFromString(\"XCTestCase\") != nil\n"
  },
  {
    "path": "Xcodes/Backend/NotificationManager.swift",
    "chars": 3852,
    "preview": "import Foundation\nimport os.log\nimport UserNotifications\n\n/// Representation of the 3 states of the Notifications permis"
  },
  {
    "path": "Xcodes/Backend/Optional+IsNotNil.swift",
    "chars": 328,
    "preview": "import Foundation\n\nextension Optional {\n    /// Note that this is lossy when setting, so you can really only set it to n"
  },
  {
    "path": "Xcodes/Backend/Path+.swift",
    "chars": 1514,
    "preview": "import Path\nimport Foundation\n\nextension Path {\n    static let defaultXcodesApplicationSupport = Path.applicationSupport"
  },
  {
    "path": "Xcodes/Backend/Process.swift",
    "chars": 3235,
    "preview": "import Combine\nimport Foundation\nimport os.log\nimport Path\nimport XcodesKit\n\npublic typealias ProcessOutput = (status: I"
  },
  {
    "path": "Xcodes/Backend/Progress+.swift",
    "chars": 4702,
    "preview": "import os.log\nimport Foundation\n\nextension Progress {\n    var xcodesLocalizedDescription: String {\n        return locali"
  },
  {
    "path": "Xcodes/Backend/Publisher+Resumable.swift",
    "chars": 1656,
    "preview": "import Combine\nimport Foundation\n\n/// Attempt and retry a task that fails with resume data up to `maximumRetryCount` tim"
  },
  {
    "path": "Xcodes/Backend/SDKs+Xcode.swift",
    "chars": 1580,
    "preview": "//\n//  SDKs+Xcode.swift\n//  Xcodes\n//\n//  Created by Matt Kiazyk on 2023-06-05.\n//  Copyright © 2023 Robots and Pencils."
  },
  {
    "path": "Xcodes/Backend/SelectedActionType.swift",
    "chars": 849,
    "preview": "//\n//  SelectedActionType.swift\n//  Xcodes\n//\n//  Created by Matt Kiazyk on 2022-07-24.\n//  Copyright © 2022 Robots and "
  },
  {
    "path": "Xcodes/Backend/SelectedXcode.swift",
    "chars": 1576,
    "preview": "import Foundation\n\n/// As part of the unexpected way we have to use focusedValue in XcodesApp, we need to provide an `Op"
  },
  {
    "path": "Xcodes/Backend/URLRequest+Apple.swift",
    "chars": 1603,
    "preview": "import Foundation\n\nextension URL {\n    static let download = URL(string: \"https://developer.apple.com/download\")!\n    st"
  },
  {
    "path": "Xcodes/Backend/URLSession+DownloadTaskPublisher.swift",
    "chars": 2749,
    "preview": "import Combine\nimport Foundation\n\nextension URLSession {\n    /**\n     - Parameter convertible: A URL or URLRequest.\n    "
  },
  {
    "path": "Xcodes/Backend/Version+.swift",
    "chars": 1703,
    "preview": "import Version\n\npublic extension Version {\n    /// Determines if two Xcode versions should be treated equivalently. This"
  },
  {
    "path": "Xcodes/Backend/Version+Xcode.swift",
    "chars": 2868,
    "preview": "import Foundation\nimport Version\n\npublic extension Version {\n    /**\n     E.g.:\n     Xcode 10.2 Beta 4\n     Xcode 10.2 G"
  },
  {
    "path": "Xcodes/Backend/Version+XcodeReleases.swift",
    "chars": 1835,
    "preview": "import Version\nimport XcodesKit\n\nextension Version {\n    /// Initialize a Version from an XcodeReleases' XCModel.Xcode\n "
  },
  {
    "path": "Xcodes/Backend/Xcode.swift",
    "chars": 2573,
    "preview": "import AppKit\nimport Foundation\nimport Version\nimport Path\nimport XcodesKit\n\npublic struct XcodeID: Codable, Hashable, I"
  },
  {
    "path": "Xcodes/Backend/XcodeCommands.swift",
    "chars": 9406,
    "preview": "import SwiftUI\nimport XcodesKit\n\n// MARK: - CommandMenu\n\nstruct XcodeCommands: Commands {\n    // CommandMenus don't part"
  },
  {
    "path": "Xcodes/Backend/XcodeInstallState.swift",
    "chars": 584,
    "preview": "import Foundation\nimport Path\nimport XcodesKit\n\nenum XcodeInstallState: Equatable {\n    case notInstalled\n    case insta"
  },
  {
    "path": "Xcodes/Frontend/About/AboutView.swift",
    "chars": 2621,
    "preview": "import SwiftUI\n\nstruct AboutView: View {\n    let showAcknowledgementsWindow: () -> Void\n    @SwiftUI.Environment(\\.openU"
  },
  {
    "path": "Xcodes/Frontend/About/AcknowledgementsView.swift",
    "chars": 595,
    "preview": "import SwiftUI\n\nstruct AcknowledgmentsView: View {\n    \n    var body: some View {\n        ScrollingTextView(\n           "
  },
  {
    "path": "Xcodes/Frontend/About/ScrollingTextView.swift",
    "chars": 744,
    "preview": "import SwiftUI\n\nstruct ScrollingTextView: NSViewRepresentable {\n    typealias NSViewType = NSScrollView\n\n    let attribu"
  },
  {
    "path": "Xcodes/Frontend/Common/NavigationSplitViewWrapper.swift",
    "chars": 815,
    "preview": "//\n//  NavigationSplitViewWrapper.swift\n//  Xcodes\n//\n//  Created by Matt Kiazyk on 2023-12-12.\n//\n\nimport SwiftUI\n\nstru"
  },
  {
    "path": "Xcodes/Frontend/Common/ObservingProgressIndicator.swift",
    "chars": 3809,
    "preview": "import Combine\nimport SwiftUI\n\n/// A ProgressIndicator that reflects the state of a Progress object.\n/// This functional"
  },
  {
    "path": "Xcodes/Frontend/Common/ProgressButton.swift",
    "chars": 1250,
    "preview": "//\n//  ProgressButton.swift\n//  Xcodes\n//\n//  Created by Chad Sykes on 2020-12-27.\n//  Copyright © 2020 Robots and Penci"
  },
  {
    "path": "Xcodes/Frontend/Common/ProgressIndicator.swift",
    "chars": 1228,
    "preview": "import SwiftUI\nimport AppKit\n\n/// You probably want ProgressView unless you need more of NSProgressIndicator's API, whic"
  },
  {
    "path": "Xcodes/Frontend/Common/TagView.swift",
    "chars": 447,
    "preview": "//\n//  TagView.swift\n//  Xcodes\n//\n//  Created by Matt Kiazyk on 2025-06-25.//\n\n\nimport SwiftUI\n\nstruct TagView: View {\n"
  },
  {
    "path": "Xcodes/Frontend/Common/TrailingIconLabelStyle.swift",
    "chars": 485,
    "preview": "//\n//  TrailingIconLabelStyle.swift\n//  Xcodes\n//\n//  Created by Daniel Chick on 3/11/24.\n//  Copyright © 2024 Robots an"
  },
  {
    "path": "Xcodes/Frontend/Common/XcodesAlert.swift",
    "chars": 1115,
    "preview": "import Foundation\nimport XcodesKit\n\nenum XcodesAlert: Identifiable {\n    case cancelInstall(xcode: Xcode)\n    case cance"
  },
  {
    "path": "Xcodes/Frontend/Common/XcodesSheet.swift",
    "chars": 1231,
    "preview": "import Foundation\nimport AppleAPI\n\nenum XcodesSheet: Identifiable {\n    case signIn\n    case twoFactor(SecondFactorData)"
  },
  {
    "path": "Xcodes/Frontend/InfoPane/CompatibilityView.swift",
    "chars": 1307,
    "preview": "//\n//  CompatibilityView.swift\n//  Xcodes\n//\n//  Created by Duong Thai on 13/10/2023.\n//  Copyright © 2023 Robots and Pe"
  },
  {
    "path": "Xcodes/Frontend/InfoPane/CompilersView.swift",
    "chars": 1671,
    "preview": "//\n//  CompilersView.swift\n//  Xcodes\n//\n//  Created by Duong Thai on 13/10/2023.\n//  Copyright © 2023 Robots and Pencil"
  },
  {
    "path": "Xcodes/Frontend/InfoPane/CornerRadiusModifier.swift",
    "chars": 772,
    "preview": "//\n//  CornerRadiusModifier.swift\n//  Xcodes\n//\n//  Created by Matt Kiazyk on 2023-12-19.\n//\n\nimport Foundation\nimport S"
  },
  {
    "path": "Xcodes/Frontend/InfoPane/IconView.swift",
    "chars": 1261,
    "preview": "//\n//  IconView.swift\n//  Xcodes\n//\n//  Created by Duong Thai on 11/10/2023.\n//  Copyright © 2023 Robots and Pencils. Al"
  },
  {
    "path": "Xcodes/Frontend/InfoPane/IdenticalBuildView.swift",
    "chars": 1758,
    "preview": "//\n//  IdenticalBuildView.swift\n//  Xcodes\n//\n//  Created by Duong Thai on 11/10/2023.\n//  Copyright © 2023 Robots and P"
  },
  {
    "path": "Xcodes/Frontend/InfoPane/InfoPane.swift",
    "chars": 7661,
    "preview": "import AppKit\nimport XcodesKit\nimport Path\nimport SwiftUI\nimport Version\n\nstruct InfoPane: View {\n    let xcode: Xcode\n "
  },
  {
    "path": "Xcodes/Frontend/InfoPane/InfoPaneControls.swift",
    "chars": 1818,
    "preview": "//\n//  InfoPaneControls.swift\n//  Xcodes\n//\n//  Created by Duong Thai on 14/10/2023.\n//  Copyright © 2023 Robots and Pen"
  },
  {
    "path": "Xcodes/Frontend/InfoPane/InstallationStepDetailView.swift",
    "chars": 1426,
    "preview": "import SwiftUI\nimport XcodesKit\n\nstruct InstallationStepDetailView: View {\n    let installationStep: XcodeInstallationSt"
  },
  {
    "path": "Xcodes/Frontend/InfoPane/InstalledStateButtons.swift",
    "chars": 2028,
    "preview": "//\n//  InstallingStateButtons.swift\n//  Xcodes\n//\n//  Created by Duong Thai on 13/10/2023.\n//  Copyright © 2023 Robots a"
  },
  {
    "path": "Xcodes/Frontend/InfoPane/NotInstalledStateButtons.swift",
    "chars": 1226,
    "preview": "//\n//  NotInstalledStateButtonsView.swift\n//  Xcodes\n//\n//  Created by Duong Thai on 13/10/2023.\n//  Copyright © 2023 Ro"
  },
  {
    "path": "Xcodes/Frontend/InfoPane/PlatformsView.swift",
    "chars": 4932,
    "preview": "//\n//  PlatformsView.swift\n//  Xcodes\n//\n//  Created by Matt Kiazyk on 2023-12-18.\n//\n\nimport Foundation\nimport SwiftUI\n"
  },
  {
    "path": "Xcodes/Frontend/InfoPane/ReleaseDateView.swift",
    "chars": 1184,
    "preview": "//\n//  ReleaseDateView.swift\n//  Xcodes\n//\n//  Created by Duong Thai on 11/10/2023.\n//  Copyright © 2023 Robots and Penc"
  },
  {
    "path": "Xcodes/Frontend/InfoPane/ReleaseNotesView.swift",
    "chars": 898,
    "preview": "//\n//  ReleaseNotesView.swift\n//  Xcodes\n//\n//  Created by Duong Thai on 13/10/2023.\n//  Copyright © 2023 Robots and Pen"
  },
  {
    "path": "Xcodes/Frontend/InfoPane/RuntimeInstallationStepDetailView.swift",
    "chars": 1710,
    "preview": "//\n//  RuntimeInstallationStepDetailView.swift\n//  Xcodes\n//\n//  Created by Matt Kiazyk on 2023-11-23.\n//  Copyright © 2"
  },
  {
    "path": "Xcodes/Frontend/InfoPane/SDKsView.swift",
    "chars": 1816,
    "preview": "//\n//  SDKsView.swift\n//  Xcodes\n//\n//  Created by Duong Thai on 13/10/2023.\n//  Copyright © 2023 Robots and Pencils. Al"
  },
  {
    "path": "Xcodes/Frontend/InfoPane/UnselectedView.swift",
    "chars": 457,
    "preview": "//\n//  UnselectedView.swift\n//  Xcodes\n//\n//  Created by Duong Thai on 13/10/2023.\n//  Copyright © 2023 Robots and Penci"
  },
  {
    "path": "Xcodes/Frontend/MainWindow.swift",
    "chars": 12708,
    "preview": "import ErrorHandling\nimport SwiftUI\nimport XcodesKit\nimport Path\nimport Version\n\nstruct MainWindow: View {\n    @Environm"
  },
  {
    "path": "Xcodes/Frontend/Preferences/AdvancedPreferencePane.swift",
    "chars": 8059,
    "preview": "import AppleAPI\nimport SwiftUI\nimport Path\n\nstruct AdvancedPreferencePane: View {\n    @EnvironmentObject var appState: A"
  },
  {
    "path": "Xcodes/Frontend/Preferences/DownloadPreferencePane.swift",
    "chars": 2242,
    "preview": "import AppleAPI\nimport SwiftUI\n\nstruct DownloadPreferencePane: View {\n    @EnvironmentObject var appState: AppState\n    "
  },
  {
    "path": "Xcodes/Frontend/Preferences/ExperiementsPreferencePane.swift",
    "chars": 1165,
    "preview": "import AppleAPI\nimport Path\nimport SwiftUI\n\nstruct ExperimentsPreferencePane: View {\n    @EnvironmentObject var appState"
  },
  {
    "path": "Xcodes/Frontend/Preferences/GeneralPreferencePane.swift",
    "chars": 1276,
    "preview": "import AppleAPI\nimport SwiftUI\n\nstruct GeneralPreferencePane: View {\n    @EnvironmentObject var appState: AppState\n   \n "
  },
  {
    "path": "Xcodes/Frontend/Preferences/NotificationsView.swift",
    "chars": 1200,
    "preview": "import SwiftUI\n\nstruct NotificationsView: View {\n    @EnvironmentObject var appState: AppState\n    \n    var body: some V"
  },
  {
    "path": "Xcodes/Frontend/Preferences/PlatformsListView.swift",
    "chars": 2805,
    "preview": "//\n//  PlatformsListView.swift\n//  Xcodes\n//\n//  Created by Matt Kiazyk on 2023-12-20.\n//\n\nimport Foundation\nimport Swif"
  },
  {
    "path": "Xcodes/Frontend/Preferences/PreferencesView.swift",
    "chars": 1449,
    "preview": "import SwiftUI\n\nstruct PreferencesView: View {\n    private enum Tabs: Hashable {\n        case general, updates, advanced"
  },
  {
    "path": "Xcodes/Frontend/Preferences/UpdatesPreferencePane.swift",
    "chars": 5911,
    "preview": "import AppleAPI\nimport Sparkle\nimport SwiftUI\n\nstruct UpdatesPreferencePane: View {\n    @EnvironmentObject var updater: "
  },
  {
    "path": "Xcodes/Frontend/SignIn/AttributedText.swift",
    "chars": 2886,
    "preview": "import SwiftUI\n\n/// A text view that supports NSAttributedStrings, based on NSTextView.\npublic struct AttributedText: Vi"
  },
  {
    "path": "Xcodes/Frontend/SignIn/NSAttributedString+.swift",
    "chars": 1233,
    "preview": "import Foundation\n\npublic extension NSAttributedString {\n    func addingAttribute(_ attribute: NSAttributedString.Key, v"
  },
  {
    "path": "Xcodes/Frontend/SignIn/PinCodeTextView.swift",
    "chars": 7476,
    "preview": "import Cocoa\nimport SwiftUI\n\nstruct PinCodeTextField: NSViewRepresentable {\n    typealias NSViewType = PinCodeTextView\n\n"
  },
  {
    "path": "Xcodes/Frontend/SignIn/SignIn2FAView.swift",
    "chars": 2155,
    "preview": "import SwiftUI\nimport AppleAPI\n\nstruct SignIn2FAView: View {\n    @EnvironmentObject var appState: AppState\n    @Binding "
  },
  {
    "path": "Xcodes/Frontend/SignIn/SignInCredentialsView.swift",
    "chars": 2390,
    "preview": "import SwiftUI\n\nstruct SignInCredentialsView: View {\n    private enum FocusedField {\n        case username, password\n   "
  },
  {
    "path": "Xcodes/Frontend/SignIn/SignInPhoneListView.swift",
    "chars": 2893,
    "preview": "import AppleAPI\nimport SwiftUI\n\nstruct SignInPhoneListView: View {\n    @EnvironmentObject var appState: AppState\n    @Bi"
  },
  {
    "path": "Xcodes/Frontend/SignIn/SignInSMSView.swift",
    "chars": 2219,
    "preview": "import SwiftUI\nimport AppleAPI\n\nstruct SignInSMSView: View {\n    @EnvironmentObject var appState: AppState\n    @Binding "
  },
  {
    "path": "Xcodes/Frontend/SignIn/SignInSecurityKeyPinView.swift",
    "chars": 2406,
    "preview": "//\n//  SignInSecurityKeyPin.swift\n//  Xcodes\n//\n//  Created by Kino on 2024-09-26.\n//  Copyright © 2024 Robots and Penci"
  },
  {
    "path": "Xcodes/Frontend/SignIn/SignInSecurityKeyTouchView.swift",
    "chars": 1573,
    "preview": "//\n//  SignInSecurityKeyPin.swift\n//  Xcodes\n//\n//  Created by Kino on 2024-09-26.\n//  Copyright © 2024 Robots and Penci"
  },
  {
    "path": "Xcodes/Frontend/SignIn/SignedInView.swift",
    "chars": 559,
    "preview": "import SwiftUI\n\nstruct SignedInView: View {\n    @EnvironmentObject var appState: AppState\n\n    private var username: Str"
  },
  {
    "path": "Xcodes/Frontend/View+Conditional.swift",
    "chars": 232,
    "preview": "import SwiftUI\n\nextension View {\n    @ViewBuilder\n    func `if`<Other: View>(_ predicate: Bool, then: (Self) -> Other) -"
  },
  {
    "path": "Xcodes/Frontend/View+IsHidden.swift",
    "chars": 478,
    "preview": "import SwiftUI\n\nextension View {\n    @ViewBuilder\n    func isHidden(_ isHidden: Bool) -> some View {\n        if isHidden"
  },
  {
    "path": "Xcodes/Frontend/XcodeList/AppStoreButtonStyle.swift",
    "chars": 5734,
    "preview": "import SwiftUI\n\nstruct AppStoreButtonStyle: ButtonStyle {\n    var primary: Bool\n    var highlighted: Bool\n    \n    priva"
  },
  {
    "path": "Xcodes/Frontend/XcodeList/BottomStatusBar.swift",
    "chars": 2724,
    "preview": "//\n//  BottomStatusBar.swift\n//  Xcodes\n//\n//  Created by Matt Kiazyk on 2022-06-03.\n//  Copyright © 2022 Robots and Pen"
  },
  {
    "path": "Xcodes/Frontend/XcodeList/InstallationStepRowView.swift",
    "chars": 4083,
    "preview": "import SwiftUI\nimport XcodesKit\n\nstruct InstallationStepRowView: View {\n    let installationStep: XcodeInstallationStep\n"
  },
  {
    "path": "Xcodes/Frontend/XcodeList/MainToolbar.swift",
    "chars": 3214,
    "preview": "import SwiftUI\n\nstruct MainToolbarModifier: ViewModifier {\n    @EnvironmentObject var appState: AppState\n    @Binding va"
  },
  {
    "path": "Xcodes/Frontend/XcodeList/Tag.swift",
    "chars": 415,
    "preview": "import SwiftUI\n\nstruct Tag: View {\n    var text: String\n    var body: some View {\n        Text(text)\n            .foregr"
  },
  {
    "path": "Xcodes/Frontend/XcodeList/XcodeListCategory.swift",
    "chars": 961,
    "preview": "import Foundation\nimport XcodesKit\n\nenum XcodeListCategory: String, CaseIterable, Identifiable, CustomStringConvertible "
  },
  {
    "path": "Xcodes/Frontend/XcodeList/XcodeListView.swift",
    "chars": 5254,
    "preview": "import Path\nimport SwiftUI\nimport Version\n\nstruct XcodeListView: View {\n    @EnvironmentObject var appState: AppState\n  "
  },
  {
    "path": "Xcodes/Frontend/XcodeList/XcodeListViewRow.swift",
    "chars": 6529,
    "preview": "import Path\nimport SwiftUI\nimport Version\n\nstruct XcodeListViewRow: View {\n    let xcode: Xcode\n    let selected: Bool\n "
  },
  {
    "path": "Xcodes/Preview Content/Preview Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Xcodes/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1357,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"xcodes.app-icon16.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"s"
  },
  {
    "path": "Xcodes/Resources/Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Xcodes/Resources/Assets.xcassets/Icons/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Xcodes/Resources/Assets.xcassets/install.imageset/Contents.json",
    "chars": 226,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"install.pdf\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author"
  },
  {
    "path": "Xcodes/Resources/Assets.xcassets/xcode-beta.imageset/Contents.json",
    "chars": 155,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"Image.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" "
  },
  {
    "path": "Xcodes/Resources/Assets.xcassets/xcode.imageset/Contents.json",
    "chars": 155,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"xcode.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" "
  },
  {
    "path": "Xcodes/Resources/Info.plist",
    "chars": 1769,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Xcodes/Resources/Licenses.rtf",
    "chars": 87268,
    "preview": "{\\rtf1\\ansi\\ansicpg1252\\cocoartf2865\n\\cocoatextscaling0\\cocoaplatform0{\\fonttbl\\f0\\fnil\\fcharset0 .SFNS-Regular;}\n{\\colo"
  },
  {
    "path": "Xcodes/Resources/Localizable.xcstrings",
    "chars": 688072,
    "preview": "{\n  \"sourceLanguage\" : \"en\",\n  \"strings\" : {\n    \"\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit"
  },
  {
    "path": "Xcodes/Resources/Xcodes.entitlements",
    "chars": 181,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Xcodes/Resources/XcodesIcon.icon/icon.json",
    "chars": 989,
    "preview": "{\n  \"fill\" : {\n    \"automatic-gradient\" : \"extended-srgb:0.00000,0.47843,1.00000,1.00000\"\n  },\n  \"groups\" : [\n    {\n    "
  },
  {
    "path": "Xcodes/Resources/XcodesTest.entitlements",
    "chars": 181,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Xcodes/Resources/aria2c.LICENSE",
    "chars": 18002,
    "preview": "\t\t    GNU GENERAL PUBLIC LICENSE\n\t\t       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc."
  },
  {
    "path": "Xcodes/XcodesApp.swift",
    "chars": 7122,
    "preview": "import AppKit\nimport Sparkle\nimport SwiftUI\n\n@main\nstruct XcodesApp: App {\n    @NSApplicationDelegateAdaptor(AppDelegate"
  },
  {
    "path": "Xcodes/XcodesKit/.gitignore",
    "chars": 165,
    "preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\nDerivedData/\n.swiftpm/config/registries.json\n.swiftpm/xcode/package"
  },
  {
    "path": "Xcodes/XcodesKit/Package.swift",
    "chars": 1332,
    "preview": "// swift-tools-version: 5.7\n// The swift-tools-version declares the minimum version of Swift required to build this pack"
  },
  {
    "path": "Xcodes/XcodesKit/README.md",
    "chars": 44,
    "preview": "# XcodesKit\n\nA description of this package.\n"
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Extensions/Foundation.swift",
    "chars": 456,
    "preview": "import Foundation\n\nextension NSRegularExpression {\n    func firstString(in string: String, options: NSRegularExpression."
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Extensions/Logger.swift",
    "chars": 384,
    "preview": "import Foundation\nimport os.log\n\nextension Logger {\n    private static var subsystem = Bundle.main.bundleIdentifier!\n\n  "
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Models/Runtimes/CoreSimulatorImage.swift",
    "chars": 1174,
    "preview": "//\n//  CoreSimulatorImage.swift\n//  \n//\n//  Created by Matt Kiazyk on 2023-01-08.\n//\n\nimport Foundation\n\npublic struct C"
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Models/Runtimes/RuntimeInstallState.swift",
    "chars": 661,
    "preview": "//\n//  RuntimeInstallState.swift\n//\n//\n//  Created by Matt Kiazyk on 2023-11-23.\n//\n\nimport Foundation\nimport Path\n\npubl"
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Models/Runtimes/RuntimeInstallationStep.swift",
    "chars": 914,
    "preview": "//\n//  RuntimeInstallationStep.swift\n//  \n//\n//  Created by Matt Kiazyk on 2023-11-23.\n//\n\nimport Foundation\n\npublic enu"
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Models/Runtimes/Runtimes.swift",
    "chars": 5922,
    "preview": "import Foundation\n\npublic struct DownloadableRuntimesResponse: Codable {\n    public let sdkToSimulatorMappings: [SDKToSi"
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeInstallState.swift",
    "chars": 648,
    "preview": "//\n//  InstallState.swift\n//  \n//\n//  Created by Matt Kiazyk on 2023-06-06.\n//\n\nimport Foundation\nimport Path\n\npublic en"
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeInstallationStep.swift",
    "chars": 1784,
    "preview": "//\n//  InstallationStep.swift\n//  \n//\n//  Created by Matt Kiazyk on 2023-06-06.\n//\n\nimport Foundation\n\n// A numbered ste"
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeReleases/Architecture.swift",
    "chars": 1624,
    "preview": "//\n//  Architecture.swift\n//  XcodesKit\n//\n//  Created by Matt Kiazyk on 2025-08-23.\n//\n\nimport Foundation\n\n/// The name"
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeReleases/Checksums.swift",
    "chars": 319,
    "preview": "//\n//  Checksums.swift\n//  xcodereleases\n//\n//  Created by Xcode Releases on 9/17/20.\n//  Copyright © 2020 Xcode Release"
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeReleases/Compilers.swift",
    "chars": 1250,
    "preview": "//\n//  Compiler.swift\n//  xcodereleases\n//\n//  Created by Xcode Releases on 4/4/18.\n//  Copyright © 2018 Xcode Releases."
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeReleases/Link.swift",
    "chars": 800,
    "preview": "//\n//  Link.swift\n//  xcodereleases\n//\n//  Created by Xcode Releases on 4/5/18.\n//  Copyright © 2018 Xcode Releases. All"
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeReleases/Release.swift",
    "chars": 1921,
    "preview": "//\n//  Release.swift\n//  xcodereleases\n//\n//  Created by Xcode Releases on 4/4/18.\n//  Copyright © 2018 Xcode Releases. "
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeReleases/SDKs.swift",
    "chars": 2494,
    "preview": "//\n//  SDKs.swift\n//  xcodereleases\n//\n//  Created by Xcode Releases on 4/4/18.\n//  Copyright © 2018 Xcode Releases. All"
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeReleases/XcodeRelease.swift",
    "chars": 1033,
    "preview": "//\n//  Xcode.swift\n//  xcodereleases\n//\n//  Created by Xcode Releases on 4/3/18.\n//  Copyright © 2018 Xcode Releases. Al"
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeReleases/XcodeVersion.swift",
    "chars": 679,
    "preview": "//\n//  Version.swift\n//  xcodereleases\n//\n//  Created by Xcode Releases on 4/4/18.\n//  Copyright © 2018 Xcode Releases. "
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Models/XcodeReleases/YMD.swift",
    "chars": 512,
    "preview": "//\n//  YMD.swift\n//  xcodereleases\n//\n//  Created by Xcode Releases on 4/4/18.\n//  Copyright © 2018 Xcode Releases. All "
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Services/RuntimeService.swift",
    "chars": 4382,
    "preview": "import Foundation\nimport AsyncNetworkService\nimport Path\n\nextension URL {\n    static let downloadableRuntimes = URL(stri"
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Shell/Process.swift",
    "chars": 2816,
    "preview": "import Foundation\nimport Path\nimport os.log\n\npublic typealias ProcessOutput = (status: Int32, out: String, err: String)\n"
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/Shell/XcodesShell.swift",
    "chars": 1612,
    "preview": "import Foundation\nimport Path\n\npublic struct XcodesShell {\n    public var installedRuntimes: () async throws -> ProcessO"
  },
  {
    "path": "Xcodes/XcodesKit/Sources/XcodesKit/XcodesKitEnvironment.swift",
    "chars": 140,
    "preview": "import Foundation\n\npublic struct XcodesKitEnvironment {\n    public var shell = XcodesShell()\n}\n\npublic var Current = Xco"
  },
  {
    "path": "Xcodes/XcodesKit/Tests/XcodesKitTests/XcodesKitTests.swift",
    "chars": 346,
    "preview": "import XCTest\n@testable import XcodesKit\n\nfinal class XcodesKitTests: XCTestCase {\n    func testExample() throws {\n     "
  },
  {
    "path": "Xcodes.xcodeproj/project.pbxproj",
    "chars": 101602,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Xcodes.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 135,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef"
  },
  {
    "path": "Xcodes.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "chars": 238,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved",
    "chars": 4143,
    "preview": "{\n  \"object\": {\n    \"pins\": [\n      {\n        \"package\": \"AsyncNetworkService\",\n        \"repositoryURL\": \"https://github"
  },
  {
    "path": "Xcodes.xcodeproj/xcshareddata/xcschemes/Xcodes.xcscheme",
    "chars": 4463,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1220\"\n   version = \"1.7\">\n   <BuildAction\n      "
  },
  {
    "path": "Xcodes.xcodeproj/xcshareddata/xcschemes/com.robotsandpencils.XcodesApp.Helper.xcscheme",
    "chars": 2999,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1500\"\n   version = \"1.7\">\n   <BuildAction\n      "
  },
  {
    "path": "XcodesTests/AppStateTests.swift",
    "chars": 15184,
    "preview": "import AppleAPI\nimport Combine\nimport CombineExpectations\nimport Path\nimport Version\nimport XCTest\nimport XcodesKit\n\n@te"
  },
  {
    "path": "XcodesTests/AppStateUpdateTests.swift",
    "chars": 18418,
    "preview": "import Path\nimport CryptoKit\nimport Version\n@testable import Xcodes\nimport XCTest\nimport CommonCrypto\nimport BigNum\nimpo"
  },
  {
    "path": "XcodesTests/Bundle+XcodesTests.swift",
    "chars": 153,
    "preview": "import Foundation\n\nextension Bundle {\n    static var xcodesTests: Bundle {\n        Bundle(for: BundleMember.self)\n    }\n"
  },
  {
    "path": "XcodesTests/Environment+Mock.swift",
    "chars": 4335,
    "preview": "import Combine\nimport Foundation\n@testable import Xcodes\n\nextension Xcodes.Environment {\n    static var mock = Xcodes.En"
  },
  {
    "path": "XcodesTests/Fixtures/Stub-0.0.0.Info.plist",
    "chars": 319,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "XcodesTests/Fixtures/Stub-version.plist",
    "chars": 245,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "XcodesTests/Info.plist",
    "chars": 727,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "com.xcodesorg.xcodesapp.Helper/AuditTokenHack.h",
    "chars": 322,
    "preview": "// From https://github.com/securing/SimpleXPCApp/\n\n#import <Foundation/Foundation.h>\n\n@interface NSXPCConnection(Private"
  },
  {
    "path": "com.xcodesorg.xcodesapp.Helper/AuditTokenHack.m",
    "chars": 330,
    "preview": "// From https://github.com/securing/SimpleXPCApp/\n\n#import \"AuditTokenHack.h\"\n\n@implementation AuditTokenHack\n\n+ (NSData"
  },
  {
    "path": "com.xcodesorg.xcodesapp.Helper/ConnectionVerifier.swift",
    "chars": 5255,
    "preview": "// From https://github.com/securing/SimpleXPCApp/\n\nimport Foundation\nimport os.log\n\nclass ConnectionVerifier {\n    \n    "
  }
]

// ... and 13 more files (download for full content)

About this extraction

This page contains the full source code of the XcodesOrg/XcodesApp GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 213 files (1.4 MB), approximately 362.4k tokens, and a symbol index with 3 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!