Compare commits

...

232 commits

Author SHA1 Message Date
Matt Kiazyk
1a0d3353b9 update readme with v3 changes 2025-09-19 23:53:54 -05:00
Matt Kiazyk
da8d22c6d3 bad package.resolved 2025-09-19 19:56:41 -05:00
Matt Kiazyk
4f9b7f8f6a remove old libfido2swift fix 2025-09-19 19:31:16 -05:00
Matt Kiazyk
f34e291433 3.0.2 2025-09-19 19:20:34 -05:00
Matt Kiazyk
789eedcdb7 add some glass effect... why not? 2025-09-19 19:19:58 -05:00
Matt Kiazyk
f9262d9a05 fix for codesign issues with libfido2swift 2025-09-19 19:19:48 -05:00
Matt Kiazyk
4776af75ee 3.0.1 2025-09-17 23:01:03 -05:00
Matt Kiazyk
cab5533b49
Merge pull request #756 from neetrath/main
Add Thai translations
2025-09-17 22:55:26 -05:00
Matt Kiazyk
fc50db1cee fix how platforms are architecture filtered 2025-09-17 22:37:19 -05:00
Ruttanachai Auitragool
576585b7de feat: Add Thai translations 2025-09-16 23:22:44 +07:00
Matt Kiazyk
6cf9647840
Merge pull request #755 from XcodesOrg/newIcon
New Icon - who dis?
2025-09-16 10:43:43 -05:00
Matt Kiazyk
90dccd8180 new fancy app icon! 2025-09-15 23:08:51 -05:00
Matt Kiazyk
372d5b1f4e
Merge pull request #753 from XcodesOrg/matt/FixXcoded26Check
fix: xcode 26 check for Apple Silicon installs
2025-09-15 22:59:56 -05:00
Matt Kiazyk
4063796833
Merge pull request #727 from megabitsenmzq/main
Update zh_Hans translations.
2025-09-15 22:42:13 -05:00
Matt Kiazyk
be2eea0532
Merge pull request #746 from LiYanan2004/filter-ux
Improve filtering UI/UX on Xcode List
2025-09-15 22:41:32 -05:00
Matt Kiazyk
a6dec75603 fix libfido2 errors on Xcode26 thanks @anciltech 2025-09-15 22:34:19 -05:00
Matt Kiazyk
9527fb2961 fix: xcode 26 check for Apple Silicon installs 2025-09-15 22:08:49 -05:00
Matt Kiazyk
084bd8f4a1
Merge pull request #744 from leschlogl/feature/portuguese-translations
Fixes Brazilian Portuguese translations
2025-09-15 22:06:46 -05:00
Lucas Eduardo Schlögl
f93da1c502 small translation improvements 2025-09-10 14:43:20 +02:00
LiYanan2004
79ef494504 Add icon for "Installed Only" filter 2025-09-02 22:22:55 +08:00
LiYanan2004
cdf3675a5e Improve filtering UI 2025-09-02 14:40:35 +08:00
Jinyu Meng
5974a85800 Update zh_Hans translations.
# Conflicts:
#	Xcodes/Resources/Localizable.xcstrings
2025-08-29 11:45:06 +09:00
Matt Kiazyk
4ccb6e7f06
Merge pull request #741 from r1b2ns/feat/redirect-new-user-to-login
feat: improve user experience with a redirect window to sign-in
2025-08-27 08:37:19 -05:00
Rubens
22e4991f9a
chore: add new localized string in all languages 2025-08-27 10:32:20 -03:00
Lucas Eduardo Schlögl
059c51b5dd Fixes Brazilian Portuguese translations 2025-08-27 13:30:47 +02:00
Matt Kiazyk
00dcf3816e v3.0.0 2025-08-26 22:22:11 -05:00
Matt Kiazyk
08738d6912
Merge pull request #743 from XcodesOrg/XcodeArchitectures
Support Showing and Downloading Multiple Xcode Architectures.
2025-08-26 20:49:45 -05:00
Matt Kiazyk
472e36ed0f adjust names 2025-08-25 22:59:28 -05:00
Matt Kiazyk
bfb8c2cbb8 fix tests 2025-08-25 22:45:21 -05:00
Matt Kiazyk
a7b3f78813 clean up localizable strings 2025-08-25 22:37:51 -05:00
Matt Kiazyk
2e2b16e759 warning if users download silicon runtime without xcode 26 selected 2025-08-25 22:26:41 -05:00
Matt Kiazyk
0bc8e42a9b have both arch versions as the same version on list 2025-08-23 21:51:13 -06:00
Matt Kiazyk
ceae881d9a show installed xcodes specific with architectures 2025-08-23 20:11:57 -06:00
Matt Kiazyk
debc41f688 support downloading individual xcode architecture versions 2025-08-23 14:56:40 -06:00
Matt Kiazyk
4b9d86b22e Support showing multiple architectures 2025-08-23 13:35:49 -06:00
Rubens
bbec397613
feat: add button to redirect user when this try to install a xcode version without signed 2025-08-20 08:38:16 -03:00
Matt Kiazyk
a434d26921
Merge pull request #730 from XcodesOrg/matt/multipleArch
Support Xcode 26 multiple Architectures
2025-07-09 21:46:04 -05:00
Matt Kiazyk
78615418b5 update ci image 2025-07-09 21:40:33 -05:00
Matt Kiazyk
54cca28dbe clean up localizations 2025-07-09 21:36:23 -05:00
Matt Kiazyk
10ce2b8511 support filtering 2025-07-07 23:03:20 -05:00
Matt Kiazyk
14b2b3da10 support displaying multiple architectures in runtimes 2025-06-25 15:31:51 -05:00
Matt Kiazyk
f3f3bf0e4f remove cache 2025-06-09 23:04:33 -05:00
Matt Kiazyk
3621601658 update jekyll gem 2025-06-09 23:02:54 -05:00
Matt Kiazyk
9630a68224 update jekyll 2025-06-09 22:59:06 -05:00
Matt Kiazyk
227f42af37 v2.4.2b31 2025-06-09 22:21:44 -05:00
Matt Kiazyk
8fbf9eef8a
Merge pull request #678 from mahadshahib/patch-1
Update AppState.swift
2025-06-09 22:18:41 -05:00
Matt Kiazyk
b302365454
Merge pull request #655 from MultiColourPixel/main
Support FIDO2 authentication with devices that don’t have a PIN code
2025-06-09 22:10:12 -05:00
Mohammad ShahibZadeh
a61338804c
Update AppState.swift
Fix the issue where entering an email address with capital letters causes the login to fail.
2024-12-24 12:31:56 +03:30
Edgar Story
259ad0789a
Improve the support for PIN-less FIDO2 devices
- We now check if in the handling of two factor option, the option to be used is a SecurityKey. If so, check if a FIDO2 device is attached and if it needs a PIN.
- When a PIN is not required, we can just move straight onto assertation, the code for which will present the touch key UI.
- Otherwise we fallback to the original flow.
2024-11-12 09:20:38 +00:00
Edgar Story
cc03660576
Push the setting of authError to happen on MainActor
- The Xcode “Run Time Issue” breakpoint was being hit whenever an error was being set, complaining about this being set outside of the main thread.
2024-11-12 09:14:51 +00:00
Edgar Story
a43bf63aab
Add function to check if a FIDO2 device is even connected 2024-11-12 09:12:44 +00:00
Edgar Story
cfef2879b5
Add function to check if fido2 device needs a PIN 2024-11-12 09:12:21 +00:00
Edgar Story
36424a78e0
Make fido2 property a lazy var
- This object was being kept around after being created and as we need it in some other functions it made sense to make it lazy and keep it around that way.
- Arguably the FIDO2 instance could be removed after each time it’s been used, but as the FIDO2 class doesn’t have any state stored in it, it seems benign keeping it about for now.
2024-11-12 09:08:26 +00:00
Edgar Story
3d9cf73fc1
Require LibFido2Swift to be 0.1.4 at least
- This has the new functionality for checking if a device is attached.
2024-11-12 09:02:45 +00:00
Edgar Story
2dc1bcdcbb
Update requirement for LibFido2Swift library to be at least 0.1.3 2024-11-10 22:06:14 +00:00
Edgar Story
afa6dff0b9
Support FIDO authentication with devices that don’t have a PIN code set 2024-11-10 12:16:17 +00:00
Matt Kiazyk
17f3d365b8 v2.4.1b30 2024-11-04 21:47:10 -06:00
Matt Kiazyk
7b154501b6
Merge pull request #650 from kabiroberai/kabir/legacy-srp
Fix handling of s2k_fo
2024-11-04 21:41:13 -06:00
Kabir Oberai
8654756d67 comment 2024-11-04 21:21:12 -05:00
Kabir Oberai
85c9bdba55 Fix handling of s2k_fo 2024-11-04 21:14:48 -05:00
Matt Kiazyk
0a02b29a4b v2.4.0b29 2024-10-28 22:28:29 -05:00
Matt Kiazyk
3a8722c07d
Merge pull request #640 from XcodesOrg/matt/SRPLogin
Support SRP Login
2024-10-28 22:22:21 -05:00
Matt Kiazyk
29bf77007a
Merge pull request #638 from abiligiri/SRPLogin_fix
SRP Login works now
2024-10-28 22:02:04 -05:00
Matt Kiazyk
a75c54f2f6
Merge pull request #632 from abiligiri/feature/more_managed_preferences
Restrict allowed versions & hide 'Support Xcodes'
2024-10-28 22:01:37 -05:00
Matt Kiazyk
42c2c6bfc0 move to xcodesOrg/swift-srp fork 2024-10-28 21:45:07 -05:00
Matt Kiazyk
69e667cf87 some cleanup of extra prints and old code 2024-10-28 21:34:41 -05:00
Anand Biligiri
dc22b913f3 Use swift-srp from remote source
- Use from https://github.com/abiligiri/swift-srp, version 1.1.0
  This is based on latest from upstream with changes required
- Remove local copy of swift-srp
2024-10-28 16:52:25 -07:00
Anand Biligiri
9b107ec98c SRP Login works now
- Switch to use https://github.com/adam-fowler/swift-srp with some modifications
  that are local
  - Pad g value to equal size of N while calculating clientProof
- Use SHA256(plain-text-password) while computing key using PBKDF2
- Added a unit test with some sample values
2024-10-28 13:25:17 -07:00
Matt Kiazyk
2ed84ef792 clean up srp client, still not working 2024-10-24 19:34:26 -05:00
Matt Kiazyk
e04ed029de (wip) SRP Login implementation 2024-10-22 23:35:59 -05:00
Anand Biligiri
e3f996da6e Restrict allowed versions & hide 'Support Xcodes'
- Introduced a new preference keys allowedMajorVersions, hideSupportXcodes
- allowedMajorVersions defaults to Int.max (ie allow all versions till date)
- allowedMajorVersions is used to limit the number of major versions to as many as
    value set for this key. Eg: A value of 1 would allow the latest GA version and one major version before
    A value of 0 would allow only the latest GA version
    A value of 2 would allow the latest GA and previous two major versions
- allowedMajorVersions does not have preference UI
    $ defaults write com.xcodesorg.xcodesapp allowedMajorVersions 2 #to limit to current GA and previous major
    $ defaults delete com.xcodesorg.xcodesapp allowedMajorVersions  #to remove limits
- Display buildNumber in bottom status bar
2024-10-20 12:51:14 -07:00
Matt Kiazyk
aca4e0ac89 update readme 2024-10-18 09:55:36 -05:00
Matt Kiazyk
dd9a348298 Bump version 2.3 2024-10-18 08:54:38 -05:00
Matt Kiazyk
d6efd8fa74
Merge pull request #629 from XcodesOrg/dependabot/github_actions/ruby/setup-ruby-1.197.0
Bump ruby/setup-ruby from 1.190.0 to 1.197.0
2024-10-18 08:38:20 -05:00
Matt Kiazyk
b81eab0cc4
Merge pull request #625 from XcodesOrg/dependabot/github_actions/actions/cache-4.1.1
Bump actions/cache from 4.0.2 to 4.1.1
2024-10-18 08:38:09 -05:00
Matt Kiazyk
dc31fe884c
Merge pull request #628 from XcodesOrg/matt/runtimeDownloadCleanup
allow cancelling of runtime downloads, cleanup
2024-10-18 08:37:50 -05:00
Matt Kiazyk
283c1a4739
Merge pull request #621 from Kyle-Ye/bugfix/windows
Terminate Xcodes app after last window closed
2024-10-18 08:37:35 -05:00
dependabot[bot]
cac0ebfab7
Bump ruby/setup-ruby from 1.190.0 to 1.197.0
Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.190.0 to 1.197.0.
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Changelog](https://github.com/ruby/setup-ruby/blob/master/release.rb)
- [Commits](https://github.com/ruby/setup-ruby/compare/v1.190.0...v1.197.0)

---
updated-dependencies:
- dependency-name: ruby/setup-ruby
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-18 10:50:01 +00:00
Kyle
59331bcb38
Adjust TerminateAfterLastWindowClosed toggle location
Move from advanced pane into general
2024-10-17 00:32:56 +08:00
Kyle
3d6908f526
Fix Xcode warning for Localizable string 2024-10-16 21:50:16 +08:00
Kyle
b91a7e53ad
Update the missing Localizable string 2024-10-16 21:46:04 +08:00
Kyle
f8970f44d5
Add TerminateAfterLastWindowClosed toggle support in Misc 2024-10-16 14:02:55 +08:00
Matt Kiazyk
6050c78613 allow cancelling of runtime downloads, cleanup 2024-10-15 22:07:53 -05:00
Matt Kiazyk
cf85e2fc5a
Merge pull request #569 from abiligiri/feature/managed_preferences
Disallow changes to managed preferences
2024-10-15 21:38:49 -05:00
Matt Kiazyk
15630109d5
Merge pull request #598 from duffpod/main
Update Localizable.xcstrings for Russian language (3)
2024-10-15 21:36:41 -05:00
dependabot[bot]
7171b90c88
Bump actions/cache from 4.0.2 to 4.1.1
Bumps [actions/cache](https://github.com/actions/cache) from 4.0.2 to 4.1.1.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4.0.2...v4.1.1)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 10:16:07 +00:00
Kyle
3d5fdd528b
Replace WIndowGroup with Window to fix multi window and terminate issue 2024-10-13 12:11:50 +08:00
Matt Kiazyk
06558610e9
Merge pull request #593 from pan93412/l10n/update-zhtw-localization
Update translation in zh-Hant
2024-10-12 22:53:58 -05:00
Matt Kiazyk
5e5fdf0d3a
Merge pull request #609 from megabitsenmzq/main
Fix one Greek localization. Add one zh_Hans localization.
2024-10-12 22:53:08 -05:00
Matt Kiazyk
8277554048
Merge pull request #617 from kinoroy/security-key-auth
Implement Security Key Auth
2024-10-12 22:50:16 -05:00
Matt Kiazyk
ba11d4129e
Merge pull request #601 from XcodesOrg/dependabot/github_actions/ruby/setup-ruby-1.190.0
Bump ruby/setup-ruby from 1.177.1 to 1.190.0
2024-10-12 22:36:12 -05:00
Matt Kiazyk
389d8d2a63
Merge pull request #603 from LiYanan2004/context-menu-fine-tune
Update label for Cancel Install Button
2024-10-12 22:34:37 -05:00
Kino Roy
f4567bdf1e Merge branch 'main' into security-key-auth
# Conflicts:
#	Xcodes.xcodeproj/project.pbxproj
2024-10-12 20:30:34 -07:00
Kino Roy
27bbf6f2a3 Add missing localizations 2024-10-12 20:26:19 -07:00
Matt Kiazyk
c1670b250a
Merge pull request #624 from XcodesOrg/matt/updateSparkle
chore: update sparkle framework
2024-10-12 22:25:00 -05:00
Matt Kiazyk
8e78c1c8c5
Merge pull request #622 from XcodesOrg/matt/cryptexRuntimeDownloads
feat: support downloading of cryptex (ex iOS 18+) runtimes
2024-10-12 22:24:48 -05:00
Matt Kiazyk
c31a1ef6fa Merge branch 'main' into matt/cryptexRuntimeDownloads 2024-10-12 21:16:32 -05:00
Matt Kiazyk
37a86643b9 chore: update sparkle framework 2024-10-12 21:16:00 -05:00
Matt Kiazyk
c245a1e69f
Merge pull request #613 from chuganzy/ganzy/rollback-loading
Add `authenticating` to the `XcodeInstallationStep`
2024-10-12 21:15:07 -05:00
Matt Kiazyk
0f5e42b88e
Merge pull request #623 from XcodesOrg/matt/Xcode16
chore: set CI to MacOS 14, Xcode 16
2024-10-12 21:03:17 -05:00
Matt Kiazyk
dd46112769 chore: set CI to MacOS 14, Xcode 16 2024-10-12 20:57:39 -05:00
Kino Roy
c3e568e234 Add missing English localization 2024-10-12 14:13:02 -07:00
Kino Roy
f269dcbc98 Fix typo 2024-10-12 14:12:50 -07:00
Kino Roy
604358bf6c Update LibFido2Swift with Xcode 15 support 2024-10-12 14:06:48 -07:00
Takeru Chuganji
1a3ca60f46
Temporarily add all translations 2024-10-12 10:08:59 +09:00
Kyle
27d5b03321
Terminate Xcodes app after last window closed 2024-10-12 00:09:20 +08:00
Matt Kiazyk
20b9833b67 feat: support downloading of cryptex runtimes 2024-10-11 10:57:16 -05:00
Kino Roy
e855a1fb62 Implement security key auth 2024-09-28 16:25:11 -07:00
Takeru Chuganji
908e7ba3ea
Add authenticating step 2024-09-22 18:06:29 +09:00
Jinyu Meng
7daa3e600e Fix one Greek localization. Add one zh_Hans localization. 2024-09-10 18:09:59 +09:00
LiYanan2004
a936bf84b2 Update label of CancelInstallButton to better describe the intent 2024-08-17 11:37:05 +08:00
dependabot[bot]
897f449b7c
Bump ruby/setup-ruby from 1.177.1 to 1.190.0
Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.177.1 to 1.190.0.
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Changelog](https://github.com/ruby/setup-ruby/blob/master/release.rb)
- [Commits](https://github.com/ruby/setup-ruby/compare/v1.177.1...v1.190.0)

---
updated-dependencies:
- dependency-name: ruby/setup-ruby
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 10:27:15 +00:00
duffpod
5ebe24060f Update Localizable.strings for Russian language (3) 2024-07-17 21:16:04 +03:00
pan93412
13496d8a7d
Update translation
Co-Authored-By: itszero <itszero@gmail.com>
2024-07-09 09:23:56 +08:00
Yi-Jyun Pan
4b6616fdea
Update translation in zh-Hant 2024-07-08 01:52:38 +08:00
Matt Kiazyk
74237f8fc1 v 2.2.0 2024-06-28 10:38:20 -05:00
Matt Kiazyk
c94c2c1979
Merge pull request #568 from floydkim/korean-localisation
Update Korean localisation
2024-06-28 10:36:12 -05:00
Matt Kiazyk
b36165100b
Merge pull request #585 from XcodesOrg/matt/iOS18Runtimes
Fix runtime downloading
2024-06-28 10:35:34 -05:00
Matt Kiazyk
8a6f5a7deb
Merge pull request #581 from Person2099/patch-1
fix: Update GitHub URLs in help menu
2024-06-28 10:33:32 -05:00
Matt Kiazyk
600d4cbafa
Merge pull request #576 from MohamediosDev/feature/add_arabic_localization_to_app
ADD: Added Arabic Localizations To Xcodes
2024-06-28 10:26:24 -05:00
Matt Kiazyk
8811a6d0d8
Merge pull request #564 from LePips/left-align-unarchiving-pane
Left Align Unarchiving Pane
2024-06-28 10:19:52 -05:00
Matt Kiazyk
f973e7d744
Merge pull request #545 from Kyle-Ye/bugfix/dock
Fix broken DockProgress Bar
2024-06-28 10:19:32 -05:00
Anand Biligiri
6e64db26fb Disallow changes to managed preferences
- Define enumerations for preferences that can be managed in an enterprise environment using MDM
- Add methods in AppState to check for managed preferences
- Update Advanced, Download, Experiments and Update preference panes to disable controls
  to modify any of the managed preferences
- Update Xcode category list button to be disabled if preference is managed
2024-06-21 07:17:18 +05:30
Seb
97bf958301
Update XcodesApp.swift
Tiny fix in help menu to use updated GitHub URLs. Changed "https://github.com/RobotsAndPencils/XcodesApp" to "https://github.com/XcodesOrg/XcodesApp"
2024-06-15 00:28:06 +10:00
MohamediosDev
dc5f637886 ADD: Added Arabic Localizations To Xcodes 2024-06-11 19:44:04 +03:00
Matt Kiazyk
a587d53798 don't fail downloading old runtime images 2024-06-10 16:18:36 -05:00
floydkim
a433bc4f72 Update Korean localisation 2024-06-03 21:16:56 +09:00
Ethan Pippin
66deeb0d57 left align unarchiving pane 2024-05-28 11:47:50 -06:00
Matt Kiazyk
4a4b469e3f
Merge pull request #522 from LiYanan2004/swift-5.10
Resolve concurrency check warning on Swift 5.10
2024-05-24 20:39:28 -05:00
Matt Kiazyk
7628f85ba8
Merge pull request #563 from XcodesOrg/dependabot/github_actions/ruby/setup-ruby-1.177.1
Bump ruby/setup-ruby from 1.173.0 to 1.177.1
2024-05-24 20:36:45 -05:00
Matt Kiazyk
f64e97f7a2
Merge pull request #560 from mikeirvingweb/png-compression
chore: lossless png compression
2024-05-24 20:36:35 -05:00
Matt Kiazyk
1152bbfb28
Merge pull request #548 from alladinian/alladinian-readme-greek
Update README.md
2024-05-24 20:34:42 -05:00
Matt Kiazyk
33dc6347f2 after last step reset dock progress 2024-05-24 20:23:03 -05:00
Matt Kiazyk
7599178042
Merge pull request #559 from mikeirvingweb/product-name-casings
fix: product name casings
2024-05-24 16:14:37 -05:00
Matt Kiazyk
6fda52ed37
Merge pull request #547 from roddymunro/add-swiftpolyglot-version
Set explicit SwiftPolyglot tag and update clone path
2024-05-24 16:13:39 -05:00
dependabot[bot]
8f1f6e9363
Bump ruby/setup-ruby from 1.173.0 to 1.177.1
Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.173.0 to 1.177.1.
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Commits](https://github.com/ruby/setup-ruby/compare/v1.173.0...v1.177.1)

---
updated-dependencies:
- dependency-name: ruby/setup-ruby
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-23 10:30:04 +00:00
Mike Irving
848ce1d362 fix: rollback icon.png 2024-05-22 21:58:33 +01:00
Mike Irving
38ae157be5 fix: rollback A&P, App Icon, Beta Icon 2024-05-22 21:57:16 +01:00
Mike Irving
7f3e8125c0 chore: lossless png compression 2024-05-14 22:40:07 +01:00
Mike Irving
7f14a4b8b5
fix: product name casings 2024-05-14 22:18:30 +01:00
Roddy Munro
cd80053be6 Move branch argument before url 2024-05-09 19:03:56 -03:00
Vasilis Akoinoglou
021d0aa745
Update README.md
Added Greek localization to the language support table
2024-04-05 10:26:05 +03:00
Roddy Munro
180dd52eee Set explicit SwiftPolyglot tag and update clone path 2024-04-04 21:27:15 -03:00
Kyle
348c89871e
Fix broken DockProgress Bar
Update DockProgress and adopt the new Concurrency Model
2024-03-31 01:44:34 +08:00
Matt Kiazyk
21c26c9793
Merge pull request #542 from XcodesOrg/dependabot/github_actions/ruby/setup-ruby-1.173.0
Bump ruby/setup-ruby from 1.172.0 to 1.173.0
2024-03-22 10:15:28 -05:00
Matt Kiazyk
4e177b5fbf
Merge pull request #543 from XcodesOrg/dependabot/github_actions/actions/cache-4.0.2
Bump actions/cache from 4.0.1 to 4.0.2
2024-03-22 10:15:18 -05:00
Matt Kiazyk
411f76788c
Merge pull request #533 from XcodesOrg/matt/testLocalizationValidation
adds localization validation on PR's
2024-03-22 10:15:05 -05:00
Matt Kiazyk
88b15b989a Update missing translations. 2024-03-22 10:09:47 -05:00
Matt Kiazyk
45626e2789
Merge pull request #540 from marlonjames71/InfoPaneTweaks
InfoPane UI Tweaks
2024-03-22 09:57:20 -05:00
dependabot[bot]
1411bd30b3
Bump actions/cache from 4.0.1 to 4.0.2
Bumps [actions/cache](https://github.com/actions/cache) from 4.0.1 to 4.0.2.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4.0.1...v4.0.2)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-20 10:42:25 +00:00
dependabot[bot]
f3472d8cc6
Bump ruby/setup-ruby from 1.172.0 to 1.173.0
Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.172.0 to 1.173.0.
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Commits](https://github.com/ruby/setup-ruby/compare/v1.172.0...v1.173.0)

---
updated-dependencies:
- dependency-name: ruby/setup-ruby
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-20 10:42:22 +00:00
Marlon
b9d37054ea
Reduces size of screenshots even more 2024-03-20 02:06:52 -04:00
Marlon
48df320aa8
Reduces size of screenshots 2024-03-20 02:02:49 -04:00
Marlon
feac373350
Updates screenshots for README 2024-03-19 23:35:45 -04:00
Marlon
9e1eff06a3
InfoPane now responsible for its padding
- Uses content margins instead of padding when it can to avoid content clipping during scrolling
- Also fixes a few indentation inconsistencies
- Adds min width for platforms section
2024-03-19 22:51:57 -04:00
Marlon
35314ce36f
Adjusts layout of platform views 2024-03-19 22:45:51 -04:00
Matt Kiazyk
9d73cd2091 v 2.1.2 2024-03-17 09:14:19 -05:00
Matt Kiazyk
91b24fc162
Merge pull request #538 from vvisionnn/main
fix: xcode list row text hidden on macOS 14.4
2024-03-17 09:13:03 -05:00
Ydna
b3fa08b43e fix: xcode list row text hidden 2024-03-17 20:41:33 +08:00
Matt Kiazyk
c417978976
Update xcstrings.yml 2024-03-16 21:18:32 -05:00
Matt Kiazyk
aa34f3b10d allow workflow dispatch 2024-03-16 21:16:44 -05:00
Matt Kiazyk
c595eba5df Test localizable.xcstrings Validation action 2024-03-16 21:12:46 -05:00
Matt Kiazyk
61bee6c84f 2.1.1 - Rebuild because of bad public key 2024-03-16 20:39:04 -05:00
Matt Kiazyk
0aefecf9bf Make sure SUDPublicKey is the right one! 2024-03-16 20:38:20 -05:00
Matt Kiazyk
acaafaeef5 bump version 2.1.0 2024-03-16 19:33:35 -05:00
Matt Kiazyk
7bdb227f3f
Merge pull request #532 from XcodesOrg/matt/xcstrings-cleanup
chore: cleanup some localizeable.xcstrings
2024-03-16 19:25:26 -05:00
Matt Kiazyk
7cf2c6f244 chore: cleanup some localizeable.xcstrings 2024-03-16 19:25:10 -05:00
Matt Kiazyk
b17fbf56a8
Merge pull request #531 from huihuisang/fix/AttributedText-layout-issue
Replace AttributedText with Text and markdown to fix the layout issues
2024-03-16 19:02:41 -05:00
Matt Kiazyk
dbf30000ca
Merge pull request #530 from huihuisang/feature/replace-default-icon-in-info
replace default icon in info page
2024-03-16 18:52:11 -05:00
Matt Kiazyk
7359ae19ae
Merge pull request #527 from chickdan/remove_outdated_version_checks
Remove outdated version checks
2024-03-16 18:41:33 -05:00
Matt Kiazyk
c09b56086b
Merge pull request #519 from Schr0eder/update-german-localization
Updated german localization
2024-03-16 18:40:51 -05:00
huihuisang
b5e50c6eed replace AttributedText with Text to fix layout issue in preference 2024-03-16 22:42:13 +08:00
huihuisang
f4c9a6d683 replace default icon in info page 2024-03-16 19:21:52 +08:00
LiYanan2004
9a5e127387 Resolve warning on Swift 5.10 2024-03-16 10:28:16 +08:00
Daniel Chick
238f974ce3 Merge branch 'main' into remove_outdated_version_checks
# Conflicts:
#	Xcodes/Frontend/Common/NavigationSplitViewWrapper.swift
2024-03-15 13:36:24 -05:00
Matt Kiazyk
b89fc21b8b
Merge pull request #526 from chickdan/trailing_icon_release_filter
Trailing Icon for Filter Button
2024-03-15 11:44:10 -05:00
Matt Kiazyk
bf0dc9d855
Merge pull request #504 from huihuisang/main
Improve the layout of the sidebar
2024-03-15 11:23:00 -05:00
Daniel Chick
74d8f395a5 Remove comment 2024-03-11 21:54:20 -05:00
Daniel Chick
4394c48e11 Remove outdated version checks 2024-03-11 21:51:35 -05:00
Daniel Chick
f62c5139f0 Correct inconsistency in minimum supported version 2024-03-11 21:51:12 -05:00
Daniel Chick
6cd8584058 Replace label style with new trailing icon 2024-03-11 21:30:39 -05:00
Daniel Chick
96e4682c56 Add Label style with trailing icon 2024-03-11 21:29:11 -05:00
Schr0eder
f58fced154 merge conflict adaptions 2024-03-11 19:04:29 +01:00
Schr0eder
fd6baa81c4 Merge remote-tracking branch 'upstream/main' into update-german-localization 2024-03-11 18:57:44 +01:00
huihuisang
9a866acf53 Place PlatformsPocket back to XcodeListView 2024-03-10 15:35:37 +08:00
Matt Kiazyk
6f432eef7f
Merge pull request #509 from XcodesOrg/dependabot/github_actions/ruby/setup-ruby-1.172.0
Bump ruby/setup-ruby from 1.171.0 to 1.172.0
2024-03-08 14:49:14 -06:00
Matt Kiazyk
5632820780
Merge pull request #515 from XcodesOrg/dependabot/github_actions/actions/cache-4.0.1
Bump actions/cache from 4.0.0 to 4.0.1
2024-03-08 14:49:05 -06:00
Matt Kiazyk
5993385a73
Merge pull request #521 from LiYanan2004/main
Improve signing experience when using non-english keyboard
2024-03-08 14:48:50 -06:00
Matt Kiazyk
238ac71517
Merge pull request #523 from XcodesOrg/fix-missing-runtime
fix runtime not appearing when multiple runtimes exist for same build
2024-03-08 14:46:45 -06:00
Matt Kiazyk
e9a95a3e63
Merge pull request #502 from pan93412/l10n/zh-TW/230210
l10n(zh-Hant): Update translation for 2.0.2
2024-03-08 14:26:50 -06:00
Matt Kiazyk
21e2a10eec
Merge pull request #506 from alladinian/feature/Settings-window-layout
Improvements in the layout of the Settings window
2024-03-08 14:21:56 -06:00
Matt Kiazyk
bf4650edcb
Merge pull request #510 from kikiwora/update-ukrainian-localisation-for-2.0.2
Update Ukrainian localisation
2024-03-08 14:07:42 -06:00
Matt Kiazyk
14f00a0e09
Merge pull request #505 from alladinian/feature/Greek-localization
Greek language localization
2024-03-08 13:51:15 -06:00
Matt Kiazyk
d7dc67c046
Merge pull request #503 from clementpadovani/clementpadovani/info_pane/allow_selection
Allow selecting text for version info
2024-03-08 13:34:25 -06:00
Matt Kiazyk
8534e2850b fix runtime not appearing when multiple runtimes exist for same build 2024-03-08 12:16:20 -06:00
LiYanan2004
5054ead2ca Improve signing experience when using non-english keyboard 2024-03-08 12:07:06 +08:00
Schr0eder
dc691ad38c grammatical correction 2024-03-07 19:54:37 +01:00
Schr0eder
4ba5d31427 updated german localization 2024-03-07 19:46:23 +01:00
dependabot[bot]
791ae598a3
Bump actions/cache from 4.0.0 to 4.0.1
Bumps [actions/cache](https://github.com/actions/cache) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4.0.0...v4.0.1)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-01 10:56:02 +00:00
huihuisang
950045b427 add icon for Xcode beta version 2024-02-29 09:04:28 +08:00
Roman Suvorov
34d7f6834e
Update Ukrainian localisation 2024-02-21 21:18:08 +02:00
dependabot[bot]
cc9d5e9e16
Bump ruby/setup-ruby from 1.171.0 to 1.172.0
Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.171.0 to 1.172.0.
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Commits](https://github.com/ruby/setup-ruby/compare/v1.171.0...v1.172.0)

---
updated-dependencies:
- dependency-name: ruby/setup-ruby
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-21 10:58:22 +00:00
Vasilis Akoinoglou
b4e598fc49 Layout adjustments for the Settings window 2024-02-20 13:14:20 +02:00
Vasilis Akoinoglou
6b6088d58d Greek language localization 2024-02-20 12:06:15 +02:00
huihuisang
e5c4fd431a Update NavigationSplitViewWrapper.swift
update mini width of sidebar
2024-02-18 19:21:27 +08:00
huihuisang
ae55e0fb15
Merge branch 'main' into main 2024-02-18 19:13:18 +08:00
pan93412
c7f9e54796
Merge branch 'main' into l10n/zh-TW/230210 2024-02-18 03:53:41 -06:00
huihuisang
e520a96021 Improve the layout of the sidebar 2024-02-18 13:52:32 +08:00
Clément Padovani
53ec9156ff allow selecting text from the compilers 2024-02-17 18:39:10 +01:00
Clément Padovani
8d7412fd18 allow selecting text from the SDKs 2024-02-17 18:39:04 +01:00
Clément Padovani
5aed44f969 Allow selecting text from the info panel 2024-02-17 18:37:32 +01:00
Matt Kiazyk
91f75843da
Merge pull request #499 from XcodesOrg/matt/498-fix-nav-close-stucks
fix: adds back in sidebar toggle button so users don't get stuck
2024-02-16 09:09:01 -06:00
Matt Kiazyk
6f0cdc72ec
Merge pull request #482 from megabitsenmzq/main
Update zh_Hans Localizations. And more.
2024-02-16 09:08:25 -06:00
Jinyu Meng
49eb2f4cf2 Update localizations to remove old items and add "Support Xcodes". 2024-02-10 15:41:29 +09:00
Jinyu Meng
8a7597e70b Update more files. 2024-02-10 15:36:07 +09:00
Jinyu Meng
144c591a41 Use Text(verbatim:) instead. 2024-02-10 15:36:07 +09:00
Jinyu Meng
0e76a18168 Remove more useless localizations. 2024-02-10 15:36:07 +09:00
Jinyu Meng
a078b8f7d0 Update zh_Hans localizations. Remove old or useless items. 2024-02-10 15:36:07 +09:00
Jinyu Meng
a00dae9b0a Create an extension for hiding strings from localizations. 2024-02-10 15:34:31 +09:00
pan93412
c4bd1d2789
l10n(zh-Hant): Update translation for 2.0.2 2024-02-10 00:25:47 +08:00
Matt Kiazyk
ab1ebe016f
Merge pull request #497 from XcodesOrg/dependabot/github_actions/release-drafter/release-drafter-6
Bump release-drafter/release-drafter from 5 to 6
2024-02-06 22:08:03 -06:00
Matt Kiazyk
9cb4f243b0 fix: adds back in sidebar toggle button so users don't get stuck 2024-02-06 22:05:54 -06:00
dependabot[bot]
c534d2494d
Bump release-drafter/release-drafter from 5 to 6
Bumps [release-drafter/release-drafter](https://github.com/release-drafter/release-drafter) from 5 to 6.
- [Release notes](https://github.com/release-drafter/release-drafter/releases)
- [Commits](https://github.com/release-drafter/release-drafter/compare/v5...v6)

---
updated-dependencies:
- dependency-name: release-drafter/release-drafter
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-02 10:07:35 +00:00
Matt Kiazyk
19808c58e4
Merge pull request #495 from XcodesOrg/dependabot/github_actions/ruby/setup-ruby-1.171.0
Bump ruby/setup-ruby from 1.169.0 to 1.171.0
2024-02-01 21:49:52 -06:00
dependabot[bot]
b1c986af3e
Bump ruby/setup-ruby from 1.169.0 to 1.171.0
Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.169.0 to 1.171.0.
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Commits](https://github.com/ruby/setup-ruby/compare/v1.169.0...v1.171.0)

---
updated-dependencies:
- dependency-name: ruby/setup-ruby
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-01 10:19:27 +00:00
Matt Kiazyk
2a9227a0d5
Add new openCollective link to readme 2024-01-26 10:24:27 -06:00
Matt Kiazyk
71ed9a3617 Update readme 2024-01-25 23:27:26 -06:00
Matt Kiazyk
da6bfc775c update to 2.0.2 2024-01-25 22:50:56 -06:00
Matt Kiazyk
66a60fcf19 missed code sign identity? 2024-01-25 22:50:23 -06:00
Matt Kiazyk
0ad800a5db Guess we have to set the code sign identity now? 2024-01-25 22:10:52 -06:00
Matt Kiazyk
9d7f122d11 Bump version: 2.0 Release! 2024-01-25 22:04:11 -06:00
Matt Kiazyk
0252bf1fe0 clean up localization for support link 2024-01-25 22:01:40 -06:00
108 changed files with 9720 additions and 1345 deletions

View file

@ -14,16 +14,16 @@ jobs:
# If you're using actions/checkout@v4 you must set persist-credentials to false in most cases for the deployment to work correctly. # 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 persist-credentials: false
- name: Cache 📦 # - name: Cache 📦
uses: actions/cache@v4.0.0 # uses: actions/cache@v4.1.1
with: # with:
path: AppCast/vendor/bundle # path: AppCast/vendor/bundle
key: ${{ runner.os }}-gems-v1.0-${{ hashFiles('AppCast/Gemfile') }} # key: ${{ runner.os }}-gems-v1.0-${{ hashFiles('AppCast/Gemfile') }}
restore-keys: | # restore-keys: |
${{ runner.os }}-gems- # ${{ runner.os }}-gems-
- name: Setup Ruby, JRuby and TruffleRuby - name: Setup Ruby, JRuby and TruffleRuby
uses: ruby/setup-ruby@v1.169.0 uses: ruby/setup-ruby@v1.197.0
with: with:
ruby-version: '3.0' ruby-version: '3.0'

View file

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

View file

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

22
.github/workflows/xcstrings.yml vendored Normal file
View file

@ -0,0 +1,22 @@
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

View file

@ -8,7 +8,7 @@ source "https://rubygems.org"
# #
# This will help ensure the proper Jekyll version is running. # This will help ensure the proper Jekyll version is running.
# Happy Jekylling! # Happy Jekylling!
gem "jekyll", "~> 3.9.0" gem "jekyll", "~> 4.4.1"
gem "jekyll-github-metadata", group: :jekyll_plugins gem "jekyll-github-metadata", group: :jekyll_plugins

View file

@ -1,88 +1,110 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
addressable (2.8.0) addressable (2.8.7)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 7.0)
base64 (0.3.0)
bigdecimal (3.2.2)
colorator (1.1.0) colorator (1.1.0)
concurrent-ruby (1.1.7) concurrent-ruby (1.3.5)
em-websocket (0.5.2) csv (3.3.5)
em-websocket (0.5.3)
eventmachine (>= 0.12.9) eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0) http_parser.rb (~> 0)
eventmachine (1.2.7) eventmachine (1.2.7)
faraday (1.3.0) faraday (1.3.0)
faraday-net_http (~> 1.0) faraday-net_http (~> 1.0)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
ruby2_keywords ruby2_keywords
faraday-net_http (1.0.1) faraday-net_http (1.0.1)
ffi (1.14.2) ffi (1.17.2)
ffi (1.17.2-x86_64-darwin)
forwardable-extended (2.6.0) forwardable-extended (2.6.0)
http_parser.rb (0.6.0) google-protobuf (4.31.1)
i18n (0.9.5) bigdecimal
rake (>= 13)
google-protobuf (4.31.1-x86_64-darwin)
bigdecimal
rake (>= 13)
http_parser.rb (0.8.0)
i18n (1.14.7)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
jekyll (3.9.0) jekyll (4.4.1)
addressable (~> 2.4) addressable (~> 2.4)
base64 (~> 0.2)
colorator (~> 1.0) colorator (~> 1.0)
csv (~> 3.0)
em-websocket (~> 0.5) em-websocket (~> 0.5)
i18n (~> 0.7) i18n (~> 1.0)
jekyll-sass-converter (~> 1.0) jekyll-sass-converter (>= 2.0, < 4.0)
jekyll-watch (~> 2.0) jekyll-watch (~> 2.0)
kramdown (>= 1.17, < 3) json (~> 2.6)
kramdown (~> 2.3, >= 2.3.1)
kramdown-parser-gfm (~> 1.0)
liquid (~> 4.0) liquid (~> 4.0)
mercenary (~> 0.3.3) mercenary (~> 0.3, >= 0.3.6)
pathutil (~> 0.9) pathutil (~> 0.9)
rouge (>= 1.7, < 4) rouge (>= 3.0, < 5.0)
safe_yaml (~> 1.0) safe_yaml (~> 1.0)
terminal-table (>= 1.8, < 4.0)
webrick (~> 1.7)
jekyll-github-metadata (2.13.0) jekyll-github-metadata (2.13.0)
jekyll (>= 3.4, < 5.0) jekyll (>= 3.4, < 5.0)
octokit (~> 4.0, != 4.4.0) octokit (~> 4.0, != 4.4.0)
jekyll-sass-converter (1.5.2) jekyll-sass-converter (3.1.0)
sass (~> 3.4) sass-embedded (~> 1.75)
jekyll-watch (2.2.1) jekyll-watch (2.2.1)
listen (~> 3.0) listen (~> 3.0)
kramdown (2.3.1) json (2.12.2)
rexml kramdown (2.5.1)
rexml (>= 3.3.9)
kramdown-parser-gfm (1.1.0) kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0) kramdown (~> 2.0)
liquid (4.0.3) liquid (4.0.4)
listen (3.4.1) listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.3.6) mercenary (0.4.0)
multipart-post (2.1.1) multipart-post (2.1.1)
octokit (4.20.0) octokit (4.20.0)
faraday (>= 0.9) faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3) sawyer (~> 0.8.0, >= 0.5.3)
pathutil (0.16.2) pathutil (0.16.2)
forwardable-extended (~> 2.6) forwardable-extended (~> 2.6)
public_suffix (4.0.6) public_suffix (6.0.2)
rb-fsevent (0.10.4) rake (13.3.0)
rb-inotify (0.10.1) rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0) ffi (~> 1.0)
rexml (3.2.5) rexml (3.4.1)
rouge (3.26.0) rouge (4.5.2)
ruby2_keywords (0.0.2) ruby2_keywords (0.0.2)
safe_yaml (1.0.5) safe_yaml (1.0.5)
sass (3.7.4) sass-embedded (1.89.2)
sass-listen (~> 4.0.0) google-protobuf (~> 4.31)
sass-listen (4.0.0) rake (>= 13)
rb-fsevent (~> 0.9, >= 0.9.4) sass-embedded (1.89.2-x86_64-darwin)
rb-inotify (~> 0.9, >= 0.9.7) google-protobuf (~> 4.31)
sawyer (0.8.2) sawyer (0.8.2)
addressable (>= 2.3.5) addressable (>= 2.3.5)
faraday (> 0.8, < 2.0) faraday (> 0.8, < 2.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
thread_safe (0.3.6) thread_safe (0.3.6)
tzinfo (1.2.10) tzinfo (1.2.10)
thread_safe (~> 0.1) thread_safe (~> 0.1)
tzinfo-data (1.2020.6) tzinfo-data (1.2020.6)
tzinfo (>= 1.0.0) tzinfo (>= 1.0.0)
unicode-display_width (2.6.0)
wdm (0.1.1) wdm (0.1.1)
webrick (1.9.1)
PLATFORMS PLATFORMS
ruby ruby
x86_64-darwin-20 x86_64-darwin-20
DEPENDENCIES DEPENDENCIES
jekyll (~> 3.9.0) jekyll (~> 4.4.1)
jekyll-github-metadata jekyll-github-metadata
kramdown-parser-gfm kramdown-parser-gfm
tzinfo (~> 1.2) tzinfo (~> 1.2)
@ -90,4 +112,4 @@ DEPENDENCIES
wdm (~> 0.1.0) wdm (~> 0.1.0)
BUNDLED WITH BUNDLED WITH
2.2.5 2.6.9

View file

@ -7,8 +7,8 @@ We love your input! We want to make contributing to this project as easy and tra
- Proposing new features - Proposing new features
- Becoming a maintainer - Becoming a maintainer
## We Develop with Github ## We Develop with GitHub
We use github to host code, to track issues and feature requests, as well as accept pull requests. 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 ## 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: Pull requests are the best way to propose changes to the codebase We actively welcome your pull requests:
@ -23,7 +23,7 @@ Pull requests are the best way to propose changes to the codebase We actively w
## Any contributions you make will be under the MIT Software License ## 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. 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's [issues](https://github.com/robotsandpencils/xcodesapp/issues) ## 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! 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 ## Write bug reports with detail, background, and sample code

BIN
IconDark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
IconMono.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,4 +1,4 @@
<h1><img src="icon.png" align="center" width=50 height=50 /> Xcodes.app</h1> <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. The easiest way to install and switch between multiple versions of Xcode.
@ -22,6 +22,19 @@ XcodesApp is now part of the `XcodesOrg` - [read more here](nextstep.md)
- Just click a button to make a version active with `xcode-select`. - 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). - View release notes, OS compatibility, included SDKs and compilers from [Xcode Releases](https://xcodereleases.com).
- Dark/Light Mode supported - 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 ## Experiments
@ -46,12 +59,15 @@ The following languages are supported because of the following community users!
|Ukranian 🇺🇦 |[@gelosi](https://github.com/gelosi)|Japanese 🇯🇵|[@tatsuz0u](https://github.com/tatsuz0u)| |Ukranian 🇺🇦 |[@gelosi](https://github.com/gelosi)|Japanese 🇯🇵|[@tatsuz0u](https://github.com/tatsuz0u)|
|German 🇩🇪|[@drct](https://github.com/drct)|Dutch 🇳🇱|[@jfversluis](https://github/com/jfversluis)| |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)| |Brazilian Portuguese 🇧🇷|[@brunomunizaf](https://github.com/brunomunizaf)|Polish 🇵🇱|[@jakex7](https://github.com/jakex7)|
|Catalan|[@ferranabello](https://github.com/ferranabello)| |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. Want to add more languages? Simply create a PR with the updated strings file.
## Installation
Xcodes.app runs on macOS Big Sur 11.0 or later. ## 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 ### Install with Homebrew
@ -66,9 +82,17 @@ brew install --cask xcodes
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. 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 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 ## Development
You'll need macOS 13.5 Ventura and Xcode 15 in order to build and run Xcodes.app. 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 `Unxip` and `aria2` must be compiled as a universal binary
``` ```
@ -83,7 +107,6 @@ You'll need macOS 13.5 Ventura and Xcode 15 in order to build and run Xcodes.app
lipo -archs unxip lipo -archs unxip
``` ```
Notable design decisions are recorded in [DECISIONS.md](./DECISIONS.md). The Apple authentication flow is described in [Apple.paw](./Apple.paw), which will allow you to play with the API endpoints that are involved using the [Paw](https://paw.cloud) app.
[`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. [`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.
@ -147,7 +170,8 @@ popd
# Attach the zip that was created in the Product directory to the release # Attach the zip that was created in the Product directory to the release
# Publish the release # Publish the release
# Update the [Homebrew Cask](https://github.com/RobotsAndPencils/homebrew-cask/blob/master/Casks/xcodes.rb). shasum -a 256 xcodes.zip
# Update the [Homebrew Cask](https://github.com/XcodesOrg/homebrew-cask/blob/master/Casks/x/xcodes.rb).
``` ```
</details> </details>
@ -155,4 +179,4 @@ popd
[Matt Kiazyk](https://github.com/mattkiazyk) - [Twitter](https://www.twitter.com/mattkiazyk) [Matt Kiazyk](https://github.com/mattkiazyk) - [Twitter](https://www.twitter.com/mattkiazyk)
[Twitter](https://twitter.com/xcodesApp) | [GitHub](https://github.com/xcodesOrg) [Twitter](https://twitter.com/xcodesApp) | [GitHub](https://github.com/xcodesOrg) | [Mastadon](https://iosdev.space/@XcodesApp) |

View file

@ -0,0 +1,26 @@
#!/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

View file

@ -7,6 +7,10 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
15F5B8902CCF09B900705E2F /* CryptoKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15F5B88F2CCF09B900705E2F /* CryptoKit.framework */; };
33027E342CA8C18800CB387C /* LibFido2Swift in Frameworks */ = {isa = PBXBuildFile; productRef = 334A932B2CA885A400A5E079 /* LibFido2Swift */; };
3328073F2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3328073E2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift */; };
332807412CA5EA820036F691 /* SignInSecurityKeyTouchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 332807402CA5EA820036F691 /* SignInSecurityKeyTouchView.swift */; };
36741BFD291E4FDB00A85AAE /* DownloadPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36741BFC291E4FDB00A85AAE /* DownloadPreferencePane.swift */; }; 36741BFD291E4FDB00A85AAE /* DownloadPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36741BFC291E4FDB00A85AAE /* DownloadPreferencePane.swift */; };
36741BFF291E50F500A85AAE /* FileError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36741BFE291E50F500A85AAE /* FileError.swift */; }; 36741BFF291E50F500A85AAE /* FileError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36741BFE291E50F500A85AAE /* FileError.swift */; };
536CFDD2263C94DE00026CE0 /* SignedInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536CFDD1263C94DE00026CE0 /* SignedInView.swift */; }; 536CFDD2263C94DE00026CE0 /* SignedInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536CFDD1263C94DE00026CE0 /* SignedInView.swift */; };
@ -25,6 +29,7 @@
B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */; }; B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */; };
B0C6AD0B2AD9178E00E64698 /* IdenticalBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */; }; B0C6AD0B2AD9178E00E64698 /* IdenticalBuildView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */; };
B0C6AD0D2AD91D7900E64698 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0C2AD91D7900E64698 /* IconView.swift */; }; B0C6AD0D2AD91D7900E64698 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0C6AD0C2AD91D7900E64698 /* IconView.swift */; };
BDBAB7452B9FF55800694B0B /* TrailingIconLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBAB7442B9FF55800694B0B /* TrailingIconLabelStyle.swift */; };
CA11E7BA2598476C00D2EE1C /* XcodeCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */; }; CA11E7BA2598476C00D2EE1C /* XcodeCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */; };
CA2518EC25A7FF2B00F08414 /* AppStateUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2518EB25A7FF2B00F08414 /* AppStateUpdateTests.swift */; }; CA2518EC25A7FF2B00F08414 /* AppStateUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2518EB25A7FF2B00F08414 /* AppStateUpdateTests.swift */; };
CA25192A25A9644800F08414 /* XcodeInstallState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA25192925A9644800F08414 /* XcodeInstallState.swift */; }; CA25192A25A9644800F08414 /* XcodeInstallState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA25192925A9644800F08414 /* XcodeInstallState.swift */; };
@ -43,7 +48,6 @@
CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF84D2595079F00E47BAF /* ScrollingTextView.swift */; }; CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF84D2595079F00E47BAF /* ScrollingTextView.swift */; };
CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8512595080100E47BAF /* AcknowledgementsView.swift */; }; CA9FF8522595080100E47BAF /* AcknowledgementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8512595080100E47BAF /* AcknowledgementsView.swift */; };
CA9FF8662595130600E47BAF /* View+IsHidden.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8652595130600E47BAF /* View+IsHidden.swift */; }; CA9FF8662595130600E47BAF /* View+IsHidden.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF8652595130600E47BAF /* View+IsHidden.swift */; };
CA9FF86D25951C6E00E47BAF /* XCModel in Frameworks */ = {isa = PBXBuildFile; productRef = CA9FF86C25951C6E00E47BAF /* XCModel */; };
CA9FF877259528CC00E47BAF /* Version+XcodeReleases.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF876259528CC00E47BAF /* Version+XcodeReleases.swift */; }; CA9FF877259528CC00E47BAF /* Version+XcodeReleases.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF876259528CC00E47BAF /* Version+XcodeReleases.swift */; };
CA9FF87B2595293E00E47BAF /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF87A2595293E00E47BAF /* DataSource.swift */; }; CA9FF87B2595293E00E47BAF /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF87A2595293E00E47BAF /* DataSource.swift */; };
CA9FF88125955C7000E47BAF /* AvailableXcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF88025955C7000E47BAF /* AvailableXcode.swift */; }; CA9FF88125955C7000E47BAF /* AvailableXcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9FF88025955C7000E47BAF /* AvailableXcode.swift */; };
@ -110,17 +114,19 @@
CAFE4AB425B7D3AF0064FE51 /* AdvancedPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE4AB325B7D3AF0064FE51 /* AdvancedPreferencePane.swift */; }; CAFE4AB425B7D3AF0064FE51 /* AdvancedPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE4AB325B7D3AF0064FE51 /* AdvancedPreferencePane.swift */; };
CAFE4ABC25B7D54B0064FE51 /* UpdatesPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE4ABB25B7D54B0064FE51 /* UpdatesPreferencePane.swift */; }; CAFE4ABC25B7D54B0064FE51 /* UpdatesPreferencePane.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFE4ABB25B7D54B0064FE51 /* UpdatesPreferencePane.swift */; };
CAFFFED8259CDA5000903F81 /* XcodeListViewRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */; }; CAFFFED8259CDA5000903F81 /* XcodeListViewRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */; };
D93F95C12E0C8C1A00238FB5 /* TagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93F95C02E0C8C1A00238FB5 /* TagView.swift */; };
E689540325BE8C64000EBCEA /* DockProgress in Frameworks */ = {isa = PBXBuildFile; productRef = E689540225BE8C64000EBCEA /* DockProgress */; }; E689540325BE8C64000EBCEA /* DockProgress in Frameworks */ = {isa = PBXBuildFile; productRef = E689540225BE8C64000EBCEA /* DockProgress */; };
E81D7EA02805250100A205FC /* Collection+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81D7E9F2805250100A205FC /* Collection+.swift */; }; E81D7EA02805250100A205FC /* Collection+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81D7E9F2805250100A205FC /* Collection+.swift */; };
E832EAF82B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */; }; E832EAF82B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */; };
E83FDC442CBB649100679C6B /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = E83FDC432CBB649100679C6B /* Sparkle */; };
E84B7D0D2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */; }; E84B7D0D2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */; };
E84E4F522B323A5F003F3959 /* CornerRadiusModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84E4F512B323A5F003F3959 /* CornerRadiusModifier.swift */; }; E84E4F522B323A5F003F3959 /* CornerRadiusModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84E4F512B323A5F003F3959 /* CornerRadiusModifier.swift */; };
E84E4F542B333864003F3959 /* PlatformsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84E4F532B333864003F3959 /* PlatformsListView.swift */; }; E84E4F542B333864003F3959 /* PlatformsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84E4F532B333864003F3959 /* PlatformsListView.swift */; };
E84E4F572B335094003F3959 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = E84E4F562B335094003F3959 /* OrderedCollections */; }; E84E4F572B335094003F3959 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = E84E4F562B335094003F3959 /* OrderedCollections */; };
E862D43B2CC8B26F00BAA376 /* SRP in Frameworks */ = {isa = PBXBuildFile; productRef = E862D43A2CC8B26F00BAA376 /* SRP */; };
E86671272B309D2F0048559A /* PlatformsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86671262B309D2F0048559A /* PlatformsView.swift */; }; E86671272B309D2F0048559A /* PlatformsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E86671262B309D2F0048559A /* PlatformsView.swift */; };
E87AB3C52939B65E00D72F43 /* Hardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87AB3C42939B65E00D72F43 /* Hardware.swift */; }; E87AB3C52939B65E00D72F43 /* Hardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87AB3C42939B65E00D72F43 /* Hardware.swift */; };
E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87DD6EA25D053FA00D86808 /* Progress+.swift */; }; E87DD6EB25D053FA00D86808 /* Progress+.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87DD6EA25D053FA00D86808 /* Progress+.swift */; };
E891A1C42B43ACF900A1B9D1 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = E891A1C32B43ACF900A1B9D1 /* Sparkle */; };
E89342FA25EDCC17007CF557 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E89342F925EDCC17007CF557 /* NotificationManager.swift */; }; E89342FA25EDCC17007CF557 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E89342F925EDCC17007CF557 /* NotificationManager.swift */; };
E8977EA325C11E1500835F80 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8977EA225C11E1500835F80 /* PreferencesView.swift */; }; E8977EA325C11E1500835F80 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8977EA225C11E1500835F80 /* PreferencesView.swift */; };
E8B20CBF2A2EDEC20057D816 /* SDKs+Xcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8B20CBE2A2EDEC20057D816 /* SDKs+Xcode.swift */; }; E8B20CBF2A2EDEC20057D816 /* SDKs+Xcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8B20CBE2A2EDEC20057D816 /* SDKs+Xcode.swift */; };
@ -133,6 +139,7 @@
E8DA461125FAF7FB002E85EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8DA461025FAF7FB002E85EF /* NotificationsView.swift */; }; E8DA461125FAF7FB002E85EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8DA461025FAF7FB002E85EF /* NotificationsView.swift */; };
E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */; }; E8E98A9025D8631800EC89A0 /* InstallationStepRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAFBC3FF259AC17F00E2A3D8 /* InstallationStepRowView.swift */; };
E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */; }; E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */; };
E8EEAD1D2E79174F00BE67E8 /* XcodesIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = E8EEAD1C2E79174F00BE67E8 /* XcodesIcon.icon */; };
E8F44A1E296B4CD7002D6592 /* Path in Frameworks */ = {isa = PBXBuildFile; productRef = E8F44A1D296B4CD7002D6592 /* Path */; }; E8F44A1E296B4CD7002D6592 /* Path in Frameworks */ = {isa = PBXBuildFile; productRef = E8F44A1D296B4CD7002D6592 /* Path */; };
E8FA00542B5B109800769CE0 /* com.xcodesorg.xcodesapp.Helper in Copy Helper */ = {isa = PBXBuildFile; fileRef = CA9FF8AE2595967A00E47BAF /* com.xcodesorg.xcodesapp.Helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; E8FA00542B5B109800769CE0 /* com.xcodesorg.xcodesapp.Helper in Copy Helper */ = {isa = PBXBuildFile; fileRef = CA9FF8AE2595967A00E47BAF /* com.xcodesorg.xcodesapp.Helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
E8FD5727291EE4AC001E004C /* AsyncNetworkService in Frameworks */ = {isa = PBXBuildFile; productRef = E8FD5726291EE4AC001E004C /* AsyncNetworkService */; }; E8FD5727291EE4AC001E004C /* AsyncNetworkService in Frameworks */ = {isa = PBXBuildFile; productRef = E8FD5726291EE4AC001E004C /* AsyncNetworkService */; };
@ -191,6 +198,9 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
15F5B88F2CCF09B900705E2F /* CryptoKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoKit.framework; path = System/Library/Frameworks/CryptoKit.framework; sourceTree = SDKROOT; };
3328073E2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSecurityKeyPinView.swift; sourceTree = "<group>"; };
332807402CA5EA820036F691 /* SignInSecurityKeyTouchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSecurityKeyTouchView.swift; sourceTree = "<group>"; };
36741BFC291E4FDB00A85AAE /* DownloadPreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadPreferencePane.swift; sourceTree = "<group>"; }; 36741BFC291E4FDB00A85AAE /* DownloadPreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadPreferencePane.swift; sourceTree = "<group>"; };
36741BFE291E50F500A85AAE /* FileError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileError.swift; sourceTree = "<group>"; }; 36741BFE291E50F500A85AAE /* FileError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileError.swift; sourceTree = "<group>"; };
536CFDD1263C94DE00026CE0 /* SignedInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignedInView.swift; sourceTree = "<group>"; }; 536CFDD1263C94DE00026CE0 /* SignedInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignedInView.swift; sourceTree = "<group>"; };
@ -209,6 +219,7 @@
B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseDateView.swift; sourceTree = "<group>"; }; B0C6AD032AD6E65700E64698 /* ReleaseDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseDateView.swift; sourceTree = "<group>"; };
B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdenticalBuildView.swift; sourceTree = "<group>"; }; B0C6AD0A2AD9178E00E64698 /* IdenticalBuildView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdenticalBuildView.swift; sourceTree = "<group>"; };
B0C6AD0C2AD91D7900E64698 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = "<group>"; }; B0C6AD0C2AD91D7900E64698 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = "<group>"; };
BDBAB7442B9FF55800694B0B /* TrailingIconLabelStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrailingIconLabelStyle.swift; sourceTree = "<group>"; };
CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeCommands.swift; sourceTree = "<group>"; }; CA11E7B92598476C00D2EE1C /* XcodeCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeCommands.swift; sourceTree = "<group>"; };
CA2518EB25A7FF2B00F08414 /* AppStateUpdateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateUpdateTests.swift; sourceTree = "<group>"; }; CA2518EB25A7FF2B00F08414 /* AppStateUpdateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateUpdateTests.swift; sourceTree = "<group>"; };
CA25192925A9644800F08414 /* XcodeInstallState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeInstallState.swift; sourceTree = "<group>"; }; CA25192925A9644800F08414 /* XcodeInstallState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeInstallState.swift; sourceTree = "<group>"; };
@ -311,6 +322,7 @@
CAFE4ABB25B7D54B0064FE51 /* UpdatesPreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatesPreferencePane.swift; sourceTree = "<group>"; }; CAFE4ABB25B7D54B0064FE51 /* UpdatesPreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatesPreferencePane.swift; sourceTree = "<group>"; };
CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeListViewRow.swift; sourceTree = "<group>"; }; CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeListViewRow.swift; sourceTree = "<group>"; };
CAFFFEEE259CEAC400903F81 /* RingProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RingProgressViewStyle.swift; sourceTree = "<group>"; }; CAFFFEEE259CEAC400903F81 /* RingProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RingProgressViewStyle.swift; sourceTree = "<group>"; };
D93F95C02E0C8C1A00238FB5 /* TagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagView.swift; sourceTree = "<group>"; };
E81D7E9F2805250100A205FC /* Collection+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+.swift"; sourceTree = "<group>"; }; E81D7E9F2805250100A205FC /* Collection+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+.swift"; sourceTree = "<group>"; };
E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeInstallationStepDetailView.swift; sourceTree = "<group>"; }; E832EAF72B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeInstallationStepDetailView.swift; sourceTree = "<group>"; };
E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSplitViewWrapper.swift; sourceTree = "<group>"; }; E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSplitViewWrapper.swift; sourceTree = "<group>"; };
@ -330,6 +342,7 @@
E8D655BF288DD04700A139C2 /* SelectedActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedActionType.swift; sourceTree = "<group>"; }; E8D655BF288DD04700A139C2 /* SelectedActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedActionType.swift; sourceTree = "<group>"; };
E8DA461025FAF7FB002E85EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; }; E8DA461025FAF7FB002E85EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepDetailView.swift; sourceTree = "<group>"; }; E8E98A9525D863D700EC89A0 /* InstallationStepDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStepDetailView.swift; sourceTree = "<group>"; };
E8EEAD1C2E79174F00BE67E8 /* XcodesIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = XcodesIcon.icon; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -344,12 +357,14 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
15F5B8902CCF09B900705E2F /* CryptoKit.framework in Frameworks */,
33027E342CA8C18800CB387C /* LibFido2Swift in Frameworks */,
CABFA9E42592F08E00380FEE /* Version in Frameworks */, CABFA9E42592F08E00380FEE /* Version in Frameworks */,
CABFA9FD2592F13300380FEE /* LegibleError in Frameworks */, CABFA9FD2592F13300380FEE /* LegibleError in Frameworks */,
E689540325BE8C64000EBCEA /* DockProgress in Frameworks */, E689540325BE8C64000EBCEA /* DockProgress in Frameworks */,
CA9FF86D25951C6E00E47BAF /* XCModel in Frameworks */,
CABFA9F82592F0F900380FEE /* KeychainAccess in Frameworks */, CABFA9F82592F0F900380FEE /* KeychainAccess in Frameworks */,
E891A1C42B43ACF900A1B9D1 /* Sparkle in Frameworks */, E83FDC442CBB649100679C6B /* Sparkle in Frameworks */,
E862D43B2CC8B26F00BAA376 /* SRP in Frameworks */,
CAA858CD25A3D8BC00ACF8C0 /* ErrorHandling in Frameworks */, CAA858CD25A3D8BC00ACF8C0 /* ErrorHandling in Frameworks */,
E8C0EB1A291EF43E0081528A /* XcodesKit in Frameworks */, E8C0EB1A291EF43E0081528A /* XcodesKit in Frameworks */,
E8FD5727291EE4AC001E004C /* AsyncNetworkService in Frameworks */, E8FD5727291EE4AC001E004C /* AsyncNetworkService in Frameworks */,
@ -374,12 +389,14 @@
63EAA4E9259944340046AB8F /* Common */ = { 63EAA4E9259944340046AB8F /* Common */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D93F95C02E0C8C1A00238FB5 /* TagView.swift */,
CAC281CC259F97FA00B8AB0B /* ObservingProgressIndicator.swift */, CAC281CC259F97FA00B8AB0B /* ObservingProgressIndicator.swift */,
63EAA4EA259944450046AB8F /* ProgressButton.swift */, 63EAA4EA259944450046AB8F /* ProgressButton.swift */,
CA452BAF259FD9770072DFA4 /* ProgressIndicator.swift */, CA452BAF259FD9770072DFA4 /* ProgressIndicator.swift */,
536CFDD3263C9A8000026CE0 /* XcodesSheet.swift */, 536CFDD3263C9A8000026CE0 /* XcodesSheet.swift */,
53CBAB2B263DCC9100410495 /* XcodesAlert.swift */, 53CBAB2B263DCC9100410495 /* XcodesAlert.swift */,
E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */, E84B7D0C2B296A8900DBDA2B /* NavigationSplitViewWrapper.swift */,
BDBAB7442B9FF55800694B0B /* TrailingIconLabelStyle.swift */,
); );
path = Common; path = Common;
sourceTree = "<group>"; sourceTree = "<group>";
@ -405,6 +422,7 @@
CA538A12255A4F7C00E64DD7 /* Frameworks */ = { CA538A12255A4F7C00E64DD7 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
15F5B88F2CCF09B900705E2F /* CryptoKit.framework */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@ -451,6 +469,8 @@
CA735108257BF96D00EA9CF8 /* AttributedText.swift */, CA735108257BF96D00EA9CF8 /* AttributedText.swift */,
CA73510C257BFCEF00EA9CF8 /* NSAttributedString+.swift */, CA73510C257BFCEF00EA9CF8 /* NSAttributedString+.swift */,
CA5D781D257365D6008EDE9D /* PinCodeTextView.swift */, CA5D781D257365D6008EDE9D /* PinCodeTextView.swift */,
3328073E2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift */,
332807402CA5EA820036F691 /* SignInSecurityKeyTouchView.swift */,
CAA1CB34255A5AD5003FD669 /* SignInCredentialsView.swift */, CAA1CB34255A5AD5003FD669 /* SignInCredentialsView.swift */,
CAA1CB44255A5B60003FD669 /* SignIn2FAView.swift */, CAA1CB44255A5B60003FD669 /* SignIn2FAView.swift */,
CAA1CB48255A5C97003FD669 /* SignInSMSView.swift */, CAA1CB48255A5C97003FD669 /* SignInSMSView.swift */,
@ -542,6 +562,7 @@
CABFAA1D2592F7F200380FEE /* Resources */ = { CABFAA1D2592F7F200380FEE /* Resources */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E8EEAD1C2E79174F00BE67E8 /* XcodesIcon.icon */,
CAA858DA25A3E11F00ACF8C0 /* aria2-release-1.35.0.tar.gz */, CAA858DA25A3E11F00ACF8C0 /* aria2-release-1.35.0.tar.gz */,
CAA8588025A2B63A00ACF8C0 /* aria2c */, CAA8588025A2B63A00ACF8C0 /* aria2c */,
CAA8588A25A2B69300ACF8C0 /* aria2c.LICENSE */, CAA8588A25A2B69300ACF8C0 /* aria2c.LICENSE */,
@ -703,14 +724,15 @@
CABFA9ED2592F0CC00380FEE /* SwiftSoup */, CABFA9ED2592F0CC00380FEE /* SwiftSoup */,
CABFA9F72592F0F900380FEE /* KeychainAccess */, CABFA9F72592F0F900380FEE /* KeychainAccess */,
CABFA9FC2592F13300380FEE /* LegibleError */, CABFA9FC2592F13300380FEE /* LegibleError */,
CA9FF86C25951C6E00E47BAF /* XCModel */,
CAA858CC25A3D8BC00ACF8C0 /* ErrorHandling */, CAA858CC25A3D8BC00ACF8C0 /* ErrorHandling */,
E689540225BE8C64000EBCEA /* DockProgress */, E689540225BE8C64000EBCEA /* DockProgress */,
E8FD5726291EE4AC001E004C /* AsyncNetworkService */, E8FD5726291EE4AC001E004C /* AsyncNetworkService */,
E8C0EB19291EF43E0081528A /* XcodesKit */, E8C0EB19291EF43E0081528A /* XcodesKit */,
E8F44A1D296B4CD7002D6592 /* Path */, E8F44A1D296B4CD7002D6592 /* Path */,
E84E4F562B335094003F3959 /* OrderedCollections */, E84E4F562B335094003F3959 /* OrderedCollections */,
E891A1C32B43ACF900A1B9D1 /* Sparkle */, E83FDC432CBB649100679C6B /* Sparkle */,
334A932B2CA885A400A5E079 /* LibFido2Swift */,
E862D43A2CC8B26F00BAA376 /* SRP */,
); );
productName = XcodesMac; productName = XcodesMac;
productReference = CAD2E79E2449574E00113D76 /* Xcodes.app */; productReference = CAD2E79E2449574E00113D76 /* Xcodes.app */;
@ -783,6 +805,8 @@
"pt-BR", "pt-BR",
nl, nl,
pl, pl,
ar,
th,
); );
mainGroup = CAD2E7952449574E00113D76; mainGroup = CAD2E7952449574E00113D76;
packageReferences = ( packageReferences = (
@ -790,14 +814,14 @@
CABFA9EC2592F0CC00380FEE /* XCRemoteSwiftPackageReference "SwiftSoup" */, CABFA9EC2592F0CC00380FEE /* XCRemoteSwiftPackageReference "SwiftSoup" */,
CABFA9F62592F0F900380FEE /* XCRemoteSwiftPackageReference "KeychainAccess" */, CABFA9F62592F0F900380FEE /* XCRemoteSwiftPackageReference "KeychainAccess" */,
CABFA9FB2592F13300380FEE /* XCRemoteSwiftPackageReference "LegibleError" */, CABFA9FB2592F13300380FEE /* XCRemoteSwiftPackageReference "LegibleError" */,
CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */,
CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */, CAA858CB25A3D8BC00ACF8C0 /* XCRemoteSwiftPackageReference "ErrorHandling" */,
CAC28186259EE27200B8AB0B /* XCRemoteSwiftPackageReference "CombineExpectations" */, CAC28186259EE27200B8AB0B /* XCRemoteSwiftPackageReference "CombineExpectations" */,
E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */, E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */,
E8FD5725291EE4AC001E004C /* XCRemoteSwiftPackageReference "AsyncHTTPNetworkService" */, E8FD5725291EE4AC001E004C /* XCRemoteSwiftPackageReference "AsyncHTTPNetworkService" */,
E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */, E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */,
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */, E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */,
E891A1C22B43ACA400A1B9D1 /* XCRemoteSwiftPackageReference "Sparkle" */, E83FDC422CBB649100679C6B /* XCRemoteSwiftPackageReference "Sparkle" */,
33027E282CA8BB5800CB387C /* XCRemoteSwiftPackageReference "LibFido2Swift" */,
); );
productRefGroup = CAD2E79F2449574E00113D76 /* Products */; productRefGroup = CAD2E79F2449574E00113D76 /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -817,6 +841,7 @@
files = ( files = (
CAD2E7A92449575000113D76 /* Preview Assets.xcassets in Resources */, CAD2E7A92449575000113D76 /* Preview Assets.xcassets in Resources */,
9DD4FFCB2B13EC1800C974F1 /* Localizable.xcstrings in Resources */, 9DD4FFCB2B13EC1800C974F1 /* Localizable.xcstrings in Resources */,
E8EEAD1D2E79174F00BE67E8 /* XcodesIcon.icon in Resources */,
CA9FF83F2594FBC000E47BAF /* Licenses.rtf in Resources */, CA9FF83F2594FBC000E47BAF /* Licenses.rtf in Resources */,
CAA858DB25A3E11F00ACF8C0 /* aria2-release-1.35.0.tar.gz in Resources */, CAA858DB25A3E11F00ACF8C0 /* aria2-release-1.35.0.tar.gz in Resources */,
CAD2E7A62449575000113D76 /* Assets.xcassets in Resources */, CAD2E7A62449575000113D76 /* Assets.xcassets in Resources */,
@ -885,6 +910,7 @@
CABFAA492593162500380FEE /* Bundle+InfoPlistValues.swift in Sources */, CABFAA492593162500380FEE /* Bundle+InfoPlistValues.swift in Sources */,
CA9FF8662595130600E47BAF /* View+IsHidden.swift in Sources */, CA9FF8662595130600E47BAF /* View+IsHidden.swift in Sources */,
CAE4248C259A68B800B8B246 /* Optional+IsNotNil.swift in Sources */, CAE4248C259A68B800B8B246 /* Optional+IsNotNil.swift in Sources */,
3328073F2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift in Sources */,
B0C6AD0D2AD91D7900E64698 /* IconView.swift in Sources */, B0C6AD0D2AD91D7900E64698 /* IconView.swift in Sources */,
CA9FF9362595B44700E47BAF /* HelperClient.swift in Sources */, CA9FF9362595B44700E47BAF /* HelperClient.swift in Sources */,
B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */, B0C6AD042AD6E65700E64698 /* ReleaseDateView.swift in Sources */,
@ -910,7 +936,9 @@
E84E4F522B323A5F003F3959 /* CornerRadiusModifier.swift in Sources */, E84E4F522B323A5F003F3959 /* CornerRadiusModifier.swift in Sources */,
B0403CF22AD934B600137C09 /* CompatibilityView.swift in Sources */, B0403CF22AD934B600137C09 /* CompatibilityView.swift in Sources */,
B0403CFE2ADA712C00137C09 /* InfoPaneControls.swift in Sources */, B0403CFE2ADA712C00137C09 /* InfoPaneControls.swift in Sources */,
D93F95C12E0C8C1A00238FB5 /* TagView.swift in Sources */,
53CBAB2C263DCC9100410495 /* XcodesAlert.swift in Sources */, 53CBAB2C263DCC9100410495 /* XcodesAlert.swift in Sources */,
332807412CA5EA820036F691 /* SignInSecurityKeyTouchView.swift in Sources */,
CA61A6E0259835580008926E /* Xcode.swift in Sources */, CA61A6E0259835580008926E /* Xcode.swift in Sources */,
CAE4247F259A666100B8B246 /* MainWindow.swift in Sources */, CAE4247F259A666100B8B246 /* MainWindow.swift in Sources */,
CA452BB0259FD9770072DFA4 /* ProgressIndicator.swift in Sources */, CA452BB0259FD9770072DFA4 /* ProgressIndicator.swift in Sources */,
@ -919,6 +947,7 @@
CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */, CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */,
E832EAF82B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift in Sources */, E832EAF82B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift in Sources */,
CABFA9C12592EEEA00380FEE /* Version+.swift in Sources */, CABFA9C12592EEEA00380FEE /* Version+.swift in Sources */,
BDBAB7452B9FF55800694B0B /* TrailingIconLabelStyle.swift in Sources */,
E8D655C0288DD04700A139C2 /* SelectedActionType.swift in Sources */, E8D655C0288DD04700A139C2 /* SelectedActionType.swift in Sources */,
36741BFD291E4FDB00A85AAE /* DownloadPreferencePane.swift in Sources */, 36741BFD291E4FDB00A85AAE /* DownloadPreferencePane.swift in Sources */,
E84E4F542B333864003F3959 /* PlatformsListView.swift in Sources */, E84E4F542B333864003F3959 /* PlatformsListView.swift in Sources */,
@ -1054,7 +1083,7 @@
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
@ -1068,24 +1097,26 @@
CA8FB636256E154800469DA5 /* Test */ = { CA8FB636256E154800469DA5 /* Test */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = XcodesIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY; CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/XcodesTest.entitlements; CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/XcodesTest.entitlements;
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 21; CURRENT_PROJECT_VERSION = 34;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO; ENABLE_HARDENED_RUNTIME = NO;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = Xcodes/Resources/Info.plist; INFOPLIST_FILE = Xcodes/Resources/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 2.0.0; MARKETING_VERSION = 3.0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp; PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
PRODUCT_NAME = Xcodes; PRODUCT_NAME = Xcodes;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -1174,6 +1205,7 @@
buildSettings = { buildSettings = {
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY; CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CREATE_INFOPLIST_SECTION_IN_BINARY = YES; CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
DEVELOPMENT_TEAM = ZU6GR6B2FY; DEVELOPMENT_TEAM = ZU6GR6B2FY;
@ -1247,7 +1279,7 @@
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
@ -1305,7 +1337,7 @@
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
SDKROOT = macosx; SDKROOT = macosx;
@ -1318,24 +1350,26 @@
CAD2E7BD2449575100113D76 /* Debug */ = { CAD2E7BD2449575100113D76 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = XcodesIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY; CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/Xcodes.entitlements; CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/Xcodes.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 21; CURRENT_PROJECT_VERSION = 34;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = ZU6GR6B2FY; DEVELOPMENT_TEAM = ZU6GR6B2FY;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = Xcodes/Resources/Info.plist; INFOPLIST_FILE = Xcodes/Resources/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 2.0.0; MARKETING_VERSION = 3.0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp; PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
PRODUCT_NAME = Xcodes; PRODUCT_NAME = Xcodes;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -1345,24 +1379,26 @@
CAD2E7BE2449575100113D76 /* Release */ = { CAD2E7BE2449575100113D76 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = XcodesIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY; CODE_SIGNING_SUBJECT_ORGANIZATIONAL_UNIT = ZU6GR6B2FY;
CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/Xcodes.entitlements; CODE_SIGN_ENTITLEMENTS = Xcodes/Resources/Xcodes.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 21; CURRENT_PROJECT_VERSION = 34;
DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"Xcodes/Preview Content\"";
DEVELOPMENT_TEAM = ZU6GR6B2FY; DEVELOPMENT_TEAM = ZU6GR6B2FY;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = Xcodes/Resources/Info.plist; INFOPLIST_FILE = Xcodes/Resources/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 13.0; MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 2.0.0; MARKETING_VERSION = 3.0.2;
PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp; PRODUCT_BUNDLE_IDENTIFIER = com.xcodesorg.xcodesapp;
PRODUCT_NAME = Xcodes; PRODUCT_NAME = Xcodes;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -1396,6 +1432,7 @@
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = ZU6GR6B2FY; DEVELOPMENT_TEAM = ZU6GR6B2FY;
@ -1459,9 +1496,9 @@
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */ /* Begin XCRemoteSwiftPackageReference section */
CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */ = { 33027E282CA8BB5800CB387C /* XCRemoteSwiftPackageReference "LibFido2Swift" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/xcodereleases/data"; repositoryURL = "https://github.com/kinoroy/LibFido2Swift";
requirement = { requirement = {
branch = main; branch = main;
kind = branch; kind = branch;
@ -1520,7 +1557,15 @@
repositoryURL = "https://github.com/sindresorhus/DockProgress"; repositoryURL = "https://github.com/sindresorhus/DockProgress";
requirement = { requirement = {
kind = upToNextMinorVersion; kind = upToNextMinorVersion;
minimumVersion = 3.2.0; minimumVersion = 4.3.1;
};
};
E83FDC422CBB649100679C6B /* XCRemoteSwiftPackageReference "Sparkle" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/sparkle-project/Sparkle";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.6.4;
}; };
}; };
E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */ = { E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */ = {
@ -1531,14 +1576,6 @@
minimumVersion = 1.0.5; minimumVersion = 1.0.5;
}; };
}; };
E891A1C22B43ACA400A1B9D1 /* XCRemoteSwiftPackageReference "Sparkle" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/sparkle-project/Sparkle";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.5.2;
};
};
E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */ = { E8F44A1C296B4CD7002D6592 /* XCRemoteSwiftPackageReference "Path" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mxcl/Path.swift"; repositoryURL = "https://github.com/mxcl/Path.swift";
@ -1558,10 +1595,9 @@
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
CA9FF86C25951C6E00E47BAF /* XCModel */ = { 334A932B2CA885A400A5E079 /* LibFido2Swift */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = CA9FF86B25951C6E00E47BAF /* XCRemoteSwiftPackageReference "data" */; productName = LibFido2Swift;
productName = XCModel;
}; };
CAA1CB2C255A5262003FD669 /* AppleAPI */ = { CAA1CB2C255A5262003FD669 /* AppleAPI */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
@ -1602,15 +1638,19 @@
package = E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */; package = E689540125BE8C64000EBCEA /* XCRemoteSwiftPackageReference "DockProgress" */;
productName = DockProgress; productName = DockProgress;
}; };
E83FDC432CBB649100679C6B /* Sparkle */ = {
isa = XCSwiftPackageProductDependency;
package = E83FDC422CBB649100679C6B /* XCRemoteSwiftPackageReference "Sparkle" */;
productName = Sparkle;
};
E84E4F562B335094003F3959 /* OrderedCollections */ = { E84E4F562B335094003F3959 /* OrderedCollections */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */; package = E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */;
productName = OrderedCollections; productName = OrderedCollections;
}; };
E891A1C32B43ACF900A1B9D1 /* Sparkle */ = { E862D43A2CC8B26F00BAA376 /* SRP */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = E891A1C22B43ACA400A1B9D1 /* XCRemoteSwiftPackageReference "Sparkle" */; productName = SRP;
productName = Sparkle;
}; };
E8C0EB19291EF43E0081528A /* XcodesKit */ = { E8C0EB19291EF43E0081528A /* XcodesKit */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;

View file

@ -10,6 +10,15 @@
"version": null "version": null
} }
}, },
{
"package": "big-num",
"repositoryURL": "https://github.com/adam-fowler/big-num",
"state": {
"branch": null,
"revision": "5c5511ad06aeb2b97d0868f7394e14a624bfb1c7",
"version": "2.0.2"
}
},
{ {
"package": "CombineExpectations", "package": "CombineExpectations",
"repositoryURL": "https://github.com/groue/CombineExpectations", "repositoryURL": "https://github.com/groue/CombineExpectations",
@ -19,22 +28,13 @@
"version": "0.6.0" "version": "0.6.0"
} }
}, },
{
"package": "XcodeReleases",
"repositoryURL": "https://github.com/xcodereleases/data",
"state": {
"branch": "main",
"revision": "a43ad89e536d7a3da525fcc23fb182c37b756ecc",
"version": null
}
},
{ {
"package": "DockProgress", "package": "DockProgress",
"repositoryURL": "https://github.com/sindresorhus/DockProgress", "repositoryURL": "https://github.com/sindresorhus/DockProgress",
"state": { "state": {
"branch": null, "branch": null,
"revision": "7100b68571e2dafe3a06ad5905b80fc3b0107b4b", "revision": "d4f23b5a8f5ca0fac393eb7ba78c2fe3e32e52da",
"version": "3.2.0" "version": "4.3.1"
} }
}, },
{ {
@ -64,6 +64,15 @@
"version": "1.0.4" "version": "1.0.4"
} }
}, },
{
"package": "LibFido2Swift",
"repositoryURL": "https://github.com/kinoroy/LibFido2Swift",
"state": {
"branch": "main",
"revision": "b87a93300c5b35307c9f26ae490963196bd927f1",
"version": null
}
},
{ {
"package": "Path.swift", "package": "Path.swift",
"repositoryURL": "https://github.com/mxcl/Path.swift", "repositoryURL": "https://github.com/mxcl/Path.swift",
@ -78,8 +87,8 @@
"repositoryURL": "https://github.com/sparkle-project/Sparkle/", "repositoryURL": "https://github.com/sparkle-project/Sparkle/",
"state": { "state": {
"branch": null, "branch": null,
"revision": "47d3d90aee3c52b6f61d04ceae426e607df62347", "revision": "0ef1ee0220239b3776f433314515fd849025673f",
"version": "2.5.2" "version": "2.6.4"
} }
}, },
{ {
@ -91,6 +100,24 @@
"version": "1.0.5" "version": "1.0.5"
} }
}, },
{
"package": "swift-crypto",
"repositoryURL": "https://github.com/apple/swift-crypto",
"state": {
"branch": null,
"revision": "ddb07e896a2a8af79512543b1c7eb9797f8898a5",
"version": "1.1.7"
}
},
{
"package": "swift-srp",
"repositoryURL": "https://github.com/xcodesOrg/swift-srp",
"state": {
"branch": "main",
"revision": "543aa0122a0257b992f6c7d62d18a26e3dffb8fe",
"version": null
}
},
{ {
"package": "SwiftSoup", "package": "SwiftSoup",
"repositoryURL": "https://github.com/scinfu/SwiftSoup", "repositoryURL": "https://github.com/scinfu/SwiftSoup",

View file

@ -1,24 +1,26 @@
// swift-tools-version:5.3 // swift-tools-version:5.7
// The swift-tools-version declares the minimum version of Swift required to build this package. // The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription import PackageDescription
let package = Package( let package = Package(
name: "AppleAPI", name: "AppleAPI",
platforms: [.macOS(.v10_15)], platforms: [.macOS(.v11)],
products: [ products: [
// Products define the executables and libraries a package produces, and make them visible to other packages. // Products define the executables and libraries a package produces, and make them visible to other packages.
.library( .library(
name: "AppleAPI", name: "AppleAPI",
targets: ["AppleAPI"]), targets: ["AppleAPI"]),
], ],
dependencies: [], dependencies: [
.package(url: "https://github.com/xcodesOrg/swift-srp", branch: "main")
],
targets: [ targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite. // 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. // Targets can depend on other targets in this package, and on products in packages this package depends on.
.target( .target(
name: "AppleAPI", name: "AppleAPI",
dependencies: []), dependencies: [.product(name: "SRP", package: "swift-srp")]),
.testTarget( .testTarget(
name: "AppleAPITests", name: "AppleAPITests",
dependencies: ["AppleAPI"]), dependencies: ["AppleAPI"]),

View file

@ -1,5 +1,9 @@
import Foundation import Foundation
import Combine import Combine
import SRP
import Crypto
import CommonCrypto
public class Client { public class Client {
private static let authTypes = ["sa", "hsa", "non-sa", "hsa2"] private static let authTypes = ["sa", "hsa", "non-sa", "hsa2"]
@ -8,8 +12,12 @@ public class Client {
// MARK: - Login // MARK: - Login
public func login(accountName: String, password: String) -> AnyPublisher<AuthenticationState, Swift.Error> { public func srpLogin(accountName: String, password: String) -> AnyPublisher<AuthenticationState, Swift.Error> {
var serviceKey: String! 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) return Current.network.dataTask(with: URLRequest.itcServiceKey)
.map(\.data) .map(\.data)
@ -24,11 +32,45 @@ public class Client {
.map { return (serviceKey, $0)} .map { return (serviceKey, $0)}
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
.flatMap { (serviceKey, hashcash) -> AnyPublisher<URLSession.DataTaskPublisher.Output, Swift.Error> in .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()
}
return Current.network.dataTask(with: URLRequest.signIn(serviceKey: serviceKey, accountName: accountName, password: password, hashcash: hashcash)) 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 } .mapError { $0 as Swift.Error }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} catch {
return Fail(error: AuthenticationError.srpInvalidPublicKey)
.eraseToAnyPublisher()
}
} }
.flatMap { result -> AnyPublisher<AuthenticationState, Swift.Error> in .flatMap { result -> AnyPublisher<AuthenticationState, Swift.Error> in
let (data, response) = result let (data, response) = result
@ -118,7 +160,7 @@ public class Client {
case .twoStep: case .twoStep:
return Fail(error: AuthenticationError.accountUsesTwoStepAuthentication) return Fail(error: AuthenticationError.accountUsesTwoStepAuthentication)
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .twoFactor: case .twoFactor, .securityKey:
return self.handleTwoFactor(serviceKey: serviceKey, sessionID: sessionID, scnt: scnt, authOptions: authOptions) return self.handleTwoFactor(serviceKey: serviceKey, sessionID: sessionID, scnt: scnt, authOptions: authOptions)
.eraseToAnyPublisher() .eraseToAnyPublisher()
case .unknown: case .unknown:
@ -139,7 +181,10 @@ public class Client {
// SMS wasn't sent automatically because user needs to choose a phone to send to // SMS wasn't sent automatically because user needs to choose a phone to send to
} else if authOptions.canFallBackToSMS { } else if authOptions.canFallBackToSMS {
option = .smsPendingChoice option = .smsPendingChoice
// Code is shown on trusted devices // 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 { } else {
option = .codeSent option = .codeSent
} }
@ -193,6 +238,33 @@ public class Client {
.eraseToAnyPublisher() .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 // MARK: - Session
/// Use the olympus session endpoint to see if the existing session is still valid /// Use the olympus session endpoint to see if the existing session is still valid
@ -227,6 +299,49 @@ public class Client {
.mapError { $0 as Error } .mapError { $0 as Error }
.eraseToAnyPublisher() .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 // MARK: - Types
@ -252,6 +367,7 @@ public enum AuthenticationError: Swift.Error, LocalizedError, Equatable {
case notDeveloperAppleId case notDeveloperAppleId
case notAuthorized case notAuthorized
case invalidResult(resultString: String?) case invalidResult(resultString: String?)
case srpInvalidPublicKey
public var errorDescription: String? { public var errorDescription: String? {
switch self { switch self {
@ -286,6 +402,8 @@ public enum AuthenticationError: Swift.Error, LocalizedError, Equatable {
return "You are not authorized. Please Sign in with your Apple ID first." return "You are not authorized. Please Sign in with your Apple ID first."
case let .invalidResult(resultString): case let .invalidResult(resultString):
return resultString ?? "If you continue to have problems, please submit a bug report in the Help menu." return resultString ?? "If you continue to have problems, please submit a bug report in the Help menu."
case .srpInvalidPublicKey:
return "Invalid Key"
} }
} }
} }
@ -326,27 +444,37 @@ public enum TwoFactorOption: Equatable {
case smsSent(AuthOptionsResponse.TrustedPhoneNumber) case smsSent(AuthOptionsResponse.TrustedPhoneNumber)
case codeSent case codeSent
case smsPendingChoice 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 struct AuthOptionsResponse: Equatable, Decodable {
public let trustedPhoneNumbers: [TrustedPhoneNumber]? public let trustedPhoneNumbers: [TrustedPhoneNumber]?
public let trustedDevices: [TrustedDevice]? public let trustedDevices: [TrustedDevice]?
public let securityCode: SecurityCodeInfo public let securityCode: SecurityCodeInfo?
public let noTrustedDevices: Bool? public let noTrustedDevices: Bool?
public let serviceErrors: [ServiceError]? public let serviceErrors: [ServiceError]?
public let fsaChallenge: FSAChallenge?
public init( public init(
trustedPhoneNumbers: [AuthOptionsResponse.TrustedPhoneNumber]?, trustedPhoneNumbers: [AuthOptionsResponse.TrustedPhoneNumber]?,
trustedDevices: [AuthOptionsResponse.TrustedDevice]?, trustedDevices: [AuthOptionsResponse.TrustedDevice]?,
securityCode: AuthOptionsResponse.SecurityCodeInfo, securityCode: AuthOptionsResponse.SecurityCodeInfo,
noTrustedDevices: Bool? = nil, noTrustedDevices: Bool? = nil,
serviceErrors: [ServiceError]? = nil serviceErrors: [ServiceError]? = nil,
fsaChallenge: FSAChallenge? = nil
) { ) {
self.trustedPhoneNumbers = trustedPhoneNumbers self.trustedPhoneNumbers = trustedPhoneNumbers
self.trustedDevices = trustedDevices self.trustedDevices = trustedDevices
self.securityCode = securityCode self.securityCode = securityCode
self.noTrustedDevices = noTrustedDevices self.noTrustedDevices = noTrustedDevices
self.serviceErrors = serviceErrors self.serviceErrors = serviceErrors
self.fsaChallenge = fsaChallenge
} }
public var kind: Kind { public var kind: Kind {
@ -354,6 +482,8 @@ public struct AuthOptionsResponse: Equatable, Decodable {
return .twoStep return .twoStep
} else if trustedPhoneNumbers != nil { } else if trustedPhoneNumbers != nil {
return .twoFactor return .twoFactor
} else if fsaChallenge != nil {
return .securityKey
} else { } else {
return .unknown return .unknown
} }
@ -416,7 +546,7 @@ public struct AuthOptionsResponse: Equatable, Decodable {
} }
public enum Kind: Equatable { public enum Kind: Equatable {
case twoStep, twoFactor, unknown case twoStep, twoFactor, securityKey, unknown
} }
} }
@ -453,3 +583,24 @@ public struct AppleProvider: Decodable, Equatable {
public struct AppleUser: Decodable, Equatable { public struct AppleUser: Decodable, Equatable {
public let fullName: String 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()
}
}

View file

@ -9,6 +9,11 @@ public extension URL {
static let trust = URL(string: "https://idmsa.apple.com/appleauth/auth/2sv/trust")! 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 federate = URL(string: "https://idmsa.apple.com/appleauth/auth/federate")!
static let olympusSession = URL(string: "https://appstoreconnect.apple.com/olympus/v1/session")! 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 { public extension URLRequest {
@ -105,6 +110,19 @@ public extension URLRequest {
} }
return request 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 { static func trust(serviceKey: String, sessionID: String, scnt: String) -> URLRequest {
var request = URLRequest(url: .trust) var request = URLRequest(url: .trust)
@ -136,4 +154,51 @@ public extension URLRequest {
return request 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
}

View file

@ -13,8 +13,8 @@ extension AppState {
// check to see if we should auto install for the user // check to see if we should auto install for the user
public func autoInstallIfNeeded() { public func autoInstallIfNeeded() {
guard let storageValue = UserDefaults.standard.object(forKey: "autoInstallation") as? Int, let autoInstallType = AutoInstallationType(rawValue: storageValue) else { return } guard let storageValue = Current.defaults.get(forKey: "autoInstallation") as? Int, let autoInstallType = AutoInstallationType(rawValue: storageValue) else { return }
if autoInstallType == .none { return } if autoInstallType == .none { return }
// get newest xcode version // get newest xcode version
@ -227,6 +227,7 @@ extension AppState {
self.error = error self.error = error
self.presentedAlert = .generic(title: localizeString("Alert.InstallArchive.Error.Title"), message: error.legibleLocalizedDescription) self.presentedAlert = .generic(title: localizeString("Alert.InstallArchive.Error.Title"), message: error.legibleLocalizedDescription)
} }
resetDockProgressTracking()
}) })
.catch { _ in .catch { _ in
Just(installedXcode) Just(installedXcode)
@ -473,19 +474,24 @@ extension AppState {
// MARK: - Dock Progress Tracking // MARK: - Dock Progress Tracking
private func setupDockProgress() { private func setupDockProgress() {
DockProgress.progressInstance = nil Task { @MainActor in
DockProgress.style = .bar DockProgress.progressInstance = nil
DockProgress.style = .bar
let progress = Progress(totalUnitCount: AppState.totalProgressUnits)
progress.kind = .file
progress.fileOperationKind = .downloading
overallProgress = progress
DockProgress.progressInstance = overallProgress
}
let progress = Progress(totalUnitCount: AppState.totalProgressUnits)
progress.kind = .file
progress.fileOperationKind = .downloading
overallProgress = progress
DockProgress.progressInstance = overallProgress
} }
func resetDockProgressTracking() { func resetDockProgressTracking() {
DockProgress.progress = 1 // Only way to completely remove overlay with DockProgress is setting progress to complete Task { @MainActor in
DockProgress.progress = 1 // Only way to completely remove overlay with DockProgress is setting progress to complete
}
} }
// MARK: - // MARK: -
@ -496,7 +502,7 @@ extension AppState {
self.allXcodes[index].installState = .installing(step) self.allXcodes[index].installState = .installing(step)
let xcode = self.allXcodes[index] let xcode = self.allXcodes[index]
Current.notificationManager.scheduleNotification(title: xcode.id.appleDescription, body: step.description, category: .normal) Current.notificationManager.scheduleNotification(title: xcode.version.major.description + "." + xcode.version.appleDescription, body: step.description, category: .normal)
} }
} }

View file

@ -4,6 +4,7 @@ import OSLog
import Combine import Combine
import Path import Path
import AppleAPI import AppleAPI
import Version
extension AppState { extension AppState {
func updateDownloadableRuntimes() { func updateDownloadableRuntimes() {
@ -15,10 +16,10 @@ extension AppState {
var updatedRuntime = runtime var updatedRuntime = runtime
// This loops through and matches up the simulatorVersion to the mappings // This loops through and matches up the simulatorVersion to the mappings
let simulatorBuildUpdate = downloadableRuntimes.sdkToSimulatorMappings.first { SDKToSimulatorMapping in let simulatorBuildUpdate = downloadableRuntimes.sdkToSimulatorMappings.filter { SDKToSimulatorMapping in
SDKToSimulatorMapping.simulatorBuildUpdate == runtime.simulatorVersion.buildUpdate SDKToSimulatorMapping.simulatorBuildUpdate == runtime.simulatorVersion.buildUpdate
} }
updatedRuntime.sdkBuildUpdate = simulatorBuildUpdate?.sdkBuildUpdate updatedRuntime.sdkBuildUpdate = simulatorBuildUpdate.map { $0.sdkBuildUpdate }
return updatedRuntime return updatedRuntime
} }
@ -48,6 +49,90 @@ extension AppState {
} }
func downloadRuntime(runtime: DownloadableRuntime) { 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 { runtimePublishers[runtime.identifier] = Task {
do { do {
let downloadedURL = try await downloadRunTimeFull(runtime: runtime) let downloadedURL = try await downloadRunTimeFull(runtime: runtime)
@ -57,6 +142,9 @@ extension AppState {
self.setInstallationStep(of: runtime, to: .installing) self.setInstallationStep(of: runtime, to: .installing)
} }
switch runtime.contentType { 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: case .package:
// not supported yet (do we need to for old packages?) // not supported yet (do we need to for old packages?)
throw "Installing via package not support - please install manually from \(downloadedURL.description)" throw "Installing via package not support - please install manually from \(downloadedURL.description)"
@ -80,19 +168,31 @@ extension AppState {
Logger.appState.error("Error downloading runtime: \(error.localizedDescription)") Logger.appState.error("Error downloading runtime: \(error.localizedDescription)")
DispatchQueue.main.async { DispatchQueue.main.async {
self.error = error self.error = error
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription) 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 { 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 // sets a proper cookie for runtimes
try await validateADCSession(path: runtime.downloadPath) try await validateADCSession(path: downloadPath)
let downloader = Downloader(rawValue: UserDefaults.standard.string(forKey: "downloader") ?? "aria2") ?? .aria2 let downloader = Downloader(rawValue: Current.defaults.string(forKey: "downloader") ?? "aria2") ?? .aria2
let url = URL(string: runtime.source)! let url = URL(string: source)!
let expectedRuntimePath = Path.xcodesApplicationSupport/"\(url.lastPathComponent)" 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 // 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") let aria2DownloadMetadataPath = expectedRuntimePath.parent/(expectedRuntimePath.basename() + ".aria2")
@ -123,9 +223,15 @@ extension AppState {
} }
public func downloadRuntimeWithAria2(_ runtime: DownloadableRuntime, to destination: Path, aria2Path: Path) -> AsyncThrowingStream<Progress, Error> { public func downloadRuntimeWithAria2(_ runtime: DownloadableRuntime, to destination: Path, aria2Path: Path) -> AsyncThrowingStream<Progress, Error> {
let cookies = AppleAPI.Current.network.session.configuration.httpCookieStorage?.cookies(for: runtime.url) ?? [] 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, runtime.url, destination, cookies) return Current.shell.downloadWithAria2Async(aria2Path, url, destination, cookies)
} }
@ -140,7 +246,10 @@ extension AppState {
runtimePublishers[runtime.identifier] = nil runtimePublishers[runtime.identifier] = nil
// If the download is cancelled by the user, clean up the download files that aria2 creates. // If the download is cancelled by the user, clean up the download files that aria2 creates.
let url = URL(string: runtime.source)! guard let source = runtime.source else {
return
}
let url = URL(string: source)!
let expectedRuntimePath = Path.xcodesApplicationSupport/"\(url.lastPathComponent)" let expectedRuntimePath = Path.xcodesApplicationSupport/"\(url.lastPathComponent)"
let aria2DownloadMetadataPath = expectedRuntimePath.parent/(expectedRuntimePath.basename() + ".aria2") let aria2DownloadMetadataPath = expectedRuntimePath.parent/(expectedRuntimePath.basename() + ".aria2")
@ -166,7 +275,10 @@ extension AppState {
} }
func coreSimulatorInfo(runtime: DownloadableRuntime) -> CoreSimulatorImage? { func coreSimulatorInfo(runtime: DownloadableRuntime) -> CoreSimulatorImage? {
return installedRuntimes.filter({ $0.runtimeInfo.build == runtime.simulatorVersion.buildUpdate }).first 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 { func deleteRuntime(runtime: DownloadableRuntime) async throws {

View file

@ -3,7 +3,6 @@ import Foundation
import Path import Path
import Version import Version
import SwiftSoup import SwiftSoup
import struct XCModel.Xcode
import AppleAPI import AppleAPI
import XcodesKit import XcodesKit
@ -211,8 +210,8 @@ extension AppState {
private func xcodeReleases() -> AnyPublisher<[AvailableXcode], Error> { private func xcodeReleases() -> AnyPublisher<[AvailableXcode], Error> {
Current.network.dataTask(with: URLRequest(url: URL(string: "https://xcodereleases.com/data.json")!)) Current.network.dataTask(with: URLRequest(url: URL(string: "https://xcodereleases.com/data.json")!))
.map(\.data) .map(\.data)
.decode(type: [XCModel.Xcode].self, decoder: JSONDecoder()) .decode(type: [XcodeRelease].self, decoder: JSONDecoder())
.map { xcReleasesXcodes in .map { xcReleasesXcodes in
let xcodes = xcReleasesXcodes.compactMap { xcReleasesXcode -> AvailableXcode? in let xcodes = xcReleasesXcodes.compactMap { xcReleasesXcode -> AvailableXcode? in
guard guard
let downloadURL = xcReleasesXcode.links?.download?.url, let downloadURL = xcReleasesXcode.links?.download?.url,
@ -233,7 +232,8 @@ extension AppState {
requiredMacOSVersion: xcReleasesXcode.requires, requiredMacOSVersion: xcReleasesXcode.requires,
releaseNotesURL: xcReleasesXcode.links?.notes?.url, releaseNotesURL: xcReleasesXcode.links?.notes?.url,
sdks: xcReleasesXcode.sdks, sdks: xcReleasesXcode.sdks,
compilers: xcReleasesXcode.compilers compilers: xcReleasesXcode.compilers,
architectures: xcReleasesXcode.architectures
) )
} }
return xcodes return xcodes

View file

@ -9,6 +9,27 @@ import Version
import os.log import os.log
import DockProgress import DockProgress
import XcodesKit 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 { class AppState: ObservableObject {
private let client = AppleAPI.Client() private let client = AppleAPI.Client()
@ -66,18 +87,24 @@ class AppState: ObservableObject {
} }
} }
var disableLocalPathChange: Bool { PreferenceKey.localPath.isManaged() }
@Published var installPath = "" { @Published var installPath = "" {
didSet { didSet {
Current.defaults.set(installPath, forKey: "installPath") Current.defaults.set(installPath, forKey: "installPath")
} }
} }
var disableInstallPathChange: Bool { PreferenceKey.installPath.isManaged() }
@Published var unxipExperiment = false { @Published var unxipExperiment = false {
didSet { didSet {
Current.defaults.set(unxipExperiment, forKey: "unxipExperiment") Current.defaults.set(unxipExperiment, forKey: "unxipExperiment")
} }
} }
var disableUnxipExperiment: Bool { PreferenceKey.unxipExperiment.isManaged() }
@Published var createSymLinkOnSelect = false { @Published var createSymLinkOnSelect = false {
didSet { didSet {
Current.defaults.set(createSymLinkOnSelect, forKey: "createSymLinkOnSelect") Current.defaults.set(createSymLinkOnSelect, forKey: "createSymLinkOnSelect")
@ -85,7 +112,7 @@ class AppState: ObservableObject {
} }
var createSymLinkOnSelectDisabled: Bool { var createSymLinkOnSelectDisabled: Bool {
return onSelectActionType == .rename return onSelectActionType == .rename || PreferenceKey.createSymLinkOnSelect.isManaged()
} }
@Published var onSelectActionType = SelectedActionType.none { @Published var onSelectActionType = SelectedActionType.none {
@ -98,12 +125,20 @@ class AppState: ObservableObject {
} }
} }
var onSelectActionTypeDisabled: Bool { PreferenceKey.onSelectActionType.isManaged() }
@Published var showOpenInRosettaOption = false { @Published var showOpenInRosettaOption = false {
didSet { didSet {
Current.defaults.set(showOpenInRosettaOption, forKey: "showOpenInRosettaOption") Current.defaults.set(showOpenInRosettaOption, forKey: "showOpenInRosettaOption")
} }
} }
@Published var terminateAfterLastWindowClosed = false {
didSet {
Current.defaults.set(terminateAfterLastWindowClosed, forKey: "terminateAfterLastWindowClosed")
}
}
// MARK: - Runtimes // MARK: - Runtimes
@Published var downloadableRuntimes: [DownloadableRuntime] = [] @Published var downloadableRuntimes: [DownloadableRuntime] = []
@ -112,7 +147,7 @@ class AppState: ObservableObject {
// MARK: - Publisher Cancellables // MARK: - Publisher Cancellables
var cancellables = Set<AnyCancellable>() var cancellables = Set<AnyCancellable>()
private var installationPublishers: [Version: AnyCancellable] = [:] private var installationPublishers: [XcodeID: AnyCancellable] = [:]
internal var runtimePublishers: [String: Task<(), any Error>] = [:] internal var runtimePublishers: [String: Task<(), any Error>] = [:]
private var selectPublisher: AnyCancellable? private var selectPublisher: AnyCancellable?
private var uninstallPublisher: AnyCancellable? private var uninstallPublisher: AnyCancellable?
@ -173,13 +208,14 @@ class AppState: ObservableObject {
onSelectActionType = SelectedActionType(rawValue: Current.defaults.string(forKey: "onSelectActionType") ?? "none") ?? .none onSelectActionType = SelectedActionType(rawValue: Current.defaults.string(forKey: "onSelectActionType") ?? "none") ?? .none
installPath = Current.defaults.string(forKey: "installPath") ?? Path.defaultInstallDirectory.string installPath = Current.defaults.string(forKey: "installPath") ?? Path.defaultInstallDirectory.string
showOpenInRosettaOption = Current.defaults.bool(forKey: "showOpenInRosettaOption") ?? false showOpenInRosettaOption = Current.defaults.bool(forKey: "showOpenInRosettaOption") ?? false
terminateAfterLastWindowClosed = Current.defaults.bool(forKey: "terminateAfterLastWindowClosed") ?? false
} }
// MARK: Timer // MARK: Timer
/// Runs a timer every 6 hours when app is open to check if it needs to auto install any xcodes /// Runs a timer every 6 hours when app is open to check if it needs to auto install any xcodes
func setupAutoInstallTimer() { func setupAutoInstallTimer() {
guard let storageValue = UserDefaults.standard.object(forKey: "autoInstallation") as? Int, let autoInstallType = AutoInstallationType(rawValue: storageValue) else { return } guard let storageValue = Current.defaults.get(forKey: "autoInstallation") as? Int, let autoInstallType = AutoInstallationType(rawValue: storageValue) else { return }
if autoInstallType == .none { return } if autoInstallType == .none { return }
autoInstallTimer = Timer.scheduledTimer(withTimeInterval: 60*60*6, repeats: true) { [weak self] _ in autoInstallTimer = Timer.scheduledTimer(withTimeInterval: 60*60*6, repeats: true) { [weak self] _ in
@ -242,7 +278,7 @@ class AppState: ObservableObject {
func signIn(username: String, password: String) { func signIn(username: String, password: String) {
authError = nil authError = nil
signIn(username: username, password: password) signIn(username: username.lowercased(), password: password)
.sink( .sink(
receiveCompletion: { _ in }, receiveCompletion: { _ in },
receiveValue: { _ in } receiveValue: { _ in }
@ -255,7 +291,7 @@ class AppState: ObservableObject {
Current.defaults.set(username, forKey: "username") Current.defaults.set(username, forKey: "username")
isProcessingAuthRequest = true isProcessingAuthRequest = true
return client.login(accountName: username, password: password) return client.srpLogin(accountName: username, password: password)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.handleEvents( .handleEvents(
receiveOutput: { authenticationState in receiveOutput: { authenticationState in
@ -270,11 +306,17 @@ class AppState: ObservableObject {
} }
func handleTwoFactorOption(_ option: TwoFactorOption, authOptions: AuthOptionsResponse, serviceKey: String, sessionID: String, scnt: String) { func handleTwoFactorOption(_ option: TwoFactorOption, authOptions: AuthOptionsResponse, serviceKey: String, sessionID: String, scnt: String) {
self.presentedSheet = .twoFactor(.init( let sessionData = AppleSessionData(serviceKey: serviceKey, sessionID: sessionID, scnt: scnt)
option: option,
authOptions: authOptions, if option == .securityKey, fido2DeviceIsPresent() && !fido2DeviceNeedsPin() {
sessionData: AppleSessionData(serviceKey: serviceKey, sessionID: sessionID, scnt: scnt) 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) { func requestSMS(to trustedPhoneNumber: AuthOptionsResponse.TrustedPhoneNumber, authOptions: AuthOptionsResponse, sessionData: AppleSessionData) {
@ -320,6 +362,83 @@ class AppState: ObservableObject {
.store(in: &cancellables) .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>) { private func handleAuthenticationFlowCompletion(_ completion: Subscribers.Completion<Error>) {
switch completion { switch completion {
case let .failure(error): case let .failure(error):
@ -405,10 +524,10 @@ class AppState: ObservableObject {
// MARK: - Install // MARK: - Install
func checkMinVersionAndInstall(id: Xcode.ID) { func checkMinVersionAndInstall(id: XcodeID) {
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return } guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
// Check to see if users MacOS is supported // Check to see if users macOS is supported
if let requiredMacOSVersion = availableXcode.requiredMacOSVersion { if let requiredMacOSVersion = availableXcode.requiredMacOSVersion {
if hasMinSupportedOS(requiredMacOSVersion: requiredMacOSVersion) { if hasMinSupportedOS(requiredMacOSVersion: requiredMacOSVersion) {
// prompt // prompt
@ -432,10 +551,15 @@ class AppState: ObservableObject {
return !ProcessInfo.processInfo.isOperatingSystemAtLeast(xcodeMinimumMacOSVersion) return !ProcessInfo.processInfo.isOperatingSystemAtLeast(xcodeMinimumMacOSVersion)
} }
func install(id: Xcode.ID) { func install(id: XcodeID) {
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return } guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
installationPublishers[id] = signInIfNeeded() installationPublishers[id] = signInIfNeeded()
.handleEvents(
receiveSubscription: { [unowned self] _ in
self.setInstallationStep(of: availableXcode.version, to: .authenticating)
}
)
.flatMap { [unowned self] in .flatMap { [unowned self] in
// signInIfNeeded might finish before the user actually authenticates if UI is involved. // 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, // This publisher will wait for the @Published authentication state to change to authenticated or unauthenticated before finishing,
@ -479,7 +603,7 @@ class AppState: ObservableObject {
.mapError { $0 as Error } .mapError { $0 as Error }
} }
.flatMap { [unowned self] in .flatMap { [unowned self] in
self.install(.version(availableXcode), downloader: Downloader(rawValue: UserDefaults.standard.string(forKey: "downloader") ?? "aria2") ?? .aria2) self.install(.version(availableXcode), downloader: Downloader(rawValue: Current.defaults.string(forKey: "downloader") ?? "aria2") ?? .aria2)
} }
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink( .sink(
@ -487,7 +611,11 @@ class AppState: ObservableObject {
self.installationPublishers[id] = nil self.installationPublishers[id] = nil
if case let .failure(error) = completion { 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 // 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 { 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.error = error
self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription) self.presentedAlert = .generic(title: localizeString("Alert.Install.Error.Title"), message: error.legibleLocalizedDescription)
} }
@ -503,9 +631,9 @@ class AppState: ObservableObject {
/// Skips using the username/password to log in to Apple, and simply gets a Auth Cookie used in downloading /// 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 /// As of Nov 2022 this was returning a 403 forbidden
func installWithoutLogin(id: Xcode.ID) { func installWithoutLogin(id: Xcode.ID) {
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return } guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
installationPublishers[id] = self.install(.version(availableXcode), downloader: Downloader(rawValue: UserDefaults.standard.string(forKey: "downloader") ?? "aria2") ?? .aria2) installationPublishers[id] = self.install(.version(availableXcode), downloader: Downloader(rawValue: Current.defaults.string(forKey: "downloader") ?? "aria2") ?? .aria2)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink( .sink(
receiveCompletion: { [unowned self] completion in receiveCompletion: { [unowned self] completion in
@ -526,7 +654,7 @@ class AppState: ObservableObject {
} }
func cancelInstall(id: Xcode.ID) { func cancelInstall(id: Xcode.ID) {
guard let availableXcode = availableXcodes.first(where: { $0.version == id }) else { return } guard let availableXcode = availableXcodes.first(where: { $0.xcodeID == id }) else { return }
// Cancel the publisher // Cancel the publisher
installationPublishers[id] = nil installationPublishers[id] = nil
@ -644,7 +772,7 @@ class AppState: ObservableObject {
config.allowsRunningApplicationSubstitution = false config.allowsRunningApplicationSubstitution = false
NSWorkspace.shared.openApplication(at: path.url, configuration: config) NSWorkspace.shared.openApplication(at: path.url, configuration: config)
default: default:
Logger.appState.error("\(xcode.id) is not installed") Logger.appState.error("\(xcode.id.version) is not installed")
return return
} }
} }
@ -740,7 +868,7 @@ class AppState: ObservableObject {
// If build metadata matches exactly, replace the available version with the installed version. // 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. // 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 }) { if let index = adjustedAvailableXcodes.map(\.version).firstIndex(where: { $0.buildMetadataIdentifiers == installedXcode.version.buildMetadataIdentifiers }) {
adjustedAvailableXcodes[index].version = installedXcode.version 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 // 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 // Not all prerelease Apple versions available online include build metadata
@ -748,7 +876,7 @@ class AppState: ObservableObject {
availableXcode.version.isEquivalent(to: installedXcode.version) && availableXcode.version.isEquivalent(to: installedXcode.version) &&
availableXcode.version.buildMetadataIdentifiers.isEmpty availableXcode.version.buildMetadataIdentifiers.isEmpty
}) { }) {
adjustedAvailableXcodes[index].version = installedXcode.version adjustedAvailableXcodes[index].xcodeID = installedXcode.xcodeID
} }
} }
} }
@ -765,14 +893,21 @@ class AppState: ObservableObject {
// Include this version if there's only one with this build identifier // Include this version if there's only one with this build identifier
return availableXcodesWithIdenticalBuildIdentifiers.count == 1 || return availableXcodesWithIdenticalBuildIdentifiers.count == 1 ||
// Or if there's more than one with this build identifier and this is the release version // Or if there's more than one with this build identifier and this is the release version
availableXcodesWithIdenticalBuildIdentifiers.count > 1 && availableXcode.version.prereleaseIdentifiers.isEmpty
} availableXcodesWithIdenticalBuildIdentifiers.count > 1 && (availableXcode.version.prereleaseIdentifiers.isEmpty || availableXcode.architectures?.count ?? 0 != 0)
}
.map { availableXcode -> Xcode in .map { availableXcode -> Xcode in
let installedXcode = installedXcodes.first(where: { installedXcode in let installedXcode = installedXcodes.first(where: { installedXcode in
availableXcode.version.isEquivalent(to: installedXcode.version) // 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: [Version] let identicalBuilds: [XcodeID]
let prereleaseAvailableXcodesWithIdenticalBuildIdentifiers = availableXcodes let prereleaseAvailableXcodesWithIdenticalBuildIdentifiers = availableXcodes
.filter { .filter {
return $0.version.buildMetadataIdentifiers == availableXcode.version.buildMetadataIdentifiers && return $0.version.buildMetadataIdentifiers == availableXcode.version.buildMetadataIdentifiers &&
@ -782,13 +917,13 @@ class AppState: ObservableObject {
} }
// If this is the release version, add the identical builds to it // If this is the release version, add the identical builds to it
if !prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.isEmpty, availableXcode.version.prereleaseIdentifiers.isEmpty { if !prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.isEmpty, availableXcode.version.prereleaseIdentifiers.isEmpty {
identicalBuilds = [availableXcode.version] + prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.map(\.version) identicalBuilds = [availableXcode.xcodeID] + prereleaseAvailableXcodesWithIdenticalBuildIdentifiers.map(\.xcodeID)
} else { } else {
identicalBuilds = [] identicalBuilds = []
} }
// If the existing install state is "installing", keep it // If the existing install state is "installing", keep it
let existingXcodeInstallState = allXcodes.first { $0.version == availableXcode.version && $0.installState.installing }?.installState let existingXcodeInstallState = allXcodes.first { $0.id == availableXcode.xcodeID && $0.installState.installing }?.installState
// Otherwise, determine it from whether there's an installed Xcode // Otherwise, determine it from whether there's an installed Xcode
let defaultXcodeInstallState: XcodeInstallState = installedXcode.map { .installed($0.path) } ?? .notInstalled let defaultXcodeInstallState: XcodeInstallState = installedXcode.map { .installed($0.path) } ?? .notInstalled
@ -803,7 +938,8 @@ class AppState: ObservableObject {
releaseDate: availableXcode.releaseDate, releaseDate: availableXcode.releaseDate,
sdks: availableXcode.sdks, sdks: availableXcode.sdks,
compilers: availableXcode.compilers, compilers: availableXcode.compilers,
downloadFileSize: availableXcode.fileSize downloadFileSize: availableXcode.fileSize,
architectures: availableXcode.architectures
) )
} }

View file

@ -1,11 +1,12 @@
import Foundation import Foundation
import Version import Version
import struct XCModel.SDKs import XcodesKit
import struct XCModel.Compilers
/// A version of Xcode that's available for installation /// A version of Xcode that's available for installation
public struct AvailableXcode: Codable { public struct AvailableXcode: Codable {
public var version: Version public var version: Version {
return xcodeID.version
}
public let url: URL public let url: URL
public let filename: String public let filename: String
public let releaseDate: Date? public let releaseDate: Date?
@ -14,9 +15,11 @@ public struct AvailableXcode: Codable {
public let sdks: SDKs? public let sdks: SDKs?
public let compilers: Compilers? public let compilers: Compilers?
public let fileSize: Int64? public let fileSize: Int64?
public let architectures: [Architecture]?
public var downloadPath: String { public var downloadPath: String {
return url.path return url.path
} }
public var xcodeID: XcodeID
public init( public init(
version: Version, version: Version,
@ -27,9 +30,9 @@ public struct AvailableXcode: Codable {
releaseNotesURL: URL? = nil, releaseNotesURL: URL? = nil,
sdks: SDKs? = nil, sdks: SDKs? = nil,
compilers: Compilers? = nil, compilers: Compilers? = nil,
fileSize: Int64? = nil fileSize: Int64? = nil,
architectures: [Architecture]? = nil
) { ) {
self.version = version
self.url = url self.url = url
self.filename = filename self.filename = filename
self.releaseDate = releaseDate self.releaseDate = releaseDate
@ -38,5 +41,7 @@ public struct AvailableXcode: Codable {
self.sdks = sdks self.sdks = sdks
self.compilers = compilers self.compilers = compilers
self.fileSize = fileSize self.fileSize = fileSize
self.architectures = architectures
self.xcodeID = XcodeID(version: version, architectures: architectures)
} }
} }

View file

@ -14,4 +14,6 @@ public enum DataSource: String, CaseIterable, Identifiable, CustomStringConverti
case .xcodeReleases: return "Xcode Releases" case .xcodeReleases: return "Xcode Releases"
} }
} }
var isManaged: Bool { PreferenceKey.dataSource.isManaged() }
} }

View file

@ -13,4 +13,6 @@ public enum Downloader: String, CaseIterable, Identifiable, CustomStringConverti
case .aria2: return "aria2" case .aria2: return "aria2"
} }
} }
var isManaged: Bool { PreferenceKey.downloader.isManaged() }
} }

View file

@ -116,7 +116,8 @@ public struct Shell {
return AsyncThrowingStream<Progress, Error> { continuation in return AsyncThrowingStream<Progress, Error> { continuation in
Task { Task {
var progress = Progress() // Assume progress will not have data races, so we manually opt-out isolation checks.
nonisolated(unsafe) var progress = Progress()
progress.kind = .file progress.kind = .file
progress.fileOperationKind = .downloading progress.fileOperationKind = .downloading
@ -195,6 +196,84 @@ public struct Shell {
return Process.run(unxipPath.url, workingDirectory: url.deletingLastPathComponent(), ["\(url.path)"]) return Process.run(unxipPath.url, workingDirectory: url.deletingLastPathComponent(), ["\(url.path)"])
} }
public var downloadRuntime: (String, String, String?) -> AsyncThrowingStream<Progress, Error> = { platform, version, architecture in
return AsyncThrowingStream<Progress, Error> { continuation in
Task {
// Assume progress will not have data races, so we manually opt-out isolation checks.
nonisolated(unsafe) var progress = Progress()
progress.kind = .file
progress.fileOperationKind = .downloading
var process = Process()
let xcodeBuildPath = Path.root.usr.bin.join("xcodebuild").url
process.executableURL = xcodeBuildPath
process.arguments = [
"-downloadPlatform",
"\(platform)",
"-buildVersion",
"\(version)"
]
if let architecture {
process.arguments?.append(contentsOf: [
"-architectureVariant",
"\(architecture)"
])
}
let stdOutPipe = Pipe()
process.standardOutput = stdOutPipe
let stdErrPipe = Pipe()
process.standardError = stdErrPipe
let observer = NotificationCenter.default.addObserver(
forName: .NSFileHandleDataAvailable,
object: nil,
queue: OperationQueue.main
) { note in
guard
// This should always be the case for Notification.Name.NSFileHandleDataAvailable
let handle = note.object as? FileHandle,
handle === stdOutPipe.fileHandleForReading || handle === stdErrPipe.fileHandleForReading
else { return }
defer { handle.waitForDataInBackgroundAndNotify() }
let string = String(decoding: handle.availableData, as: UTF8.self)
// TODO: fix warning. ObservingProgressView is currently tied to an updating progress
progress.updateFromXcodebuild(text: string)
continuation.yield(progress)
}
stdOutPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
stdErrPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
continuation.onTermination = { @Sendable _ in
process.terminate()
NotificationCenter.default.removeObserver(observer, name: .NSFileHandleDataAvailable, object: nil)
}
do {
try process.run()
} catch {
continuation.finish(throwing: error)
}
process.waitUntilExit()
NotificationCenter.default.removeObserver(observer, name: .NSFileHandleDataAvailable, object: nil)
guard process.terminationReason == .exit, process.terminationStatus == 0 else {
continuation.finish(throwing: ProcessExecutionError(process: process, standardOutput: "", standardError: ""))
return
}
continuation.finish()
}
}
}
} }
public struct Files { public struct Files {

View file

@ -1,12 +1,17 @@
import Foundation import Foundation
import Version import Version
import Path import Path
import XcodesKit
/// A version of Xcode that's already installed /// A version of Xcode that's already installed
public struct InstalledXcode: Equatable { public struct InstalledXcode: Equatable {
public let path: Path public let path: Path
public let xcodeID: XcodeID
/// Composed of the bundle short version from Info.plist and the product build version from version.plist /// Composed of the bundle short version from Info.plist and the product build version from version.plist
public let version: Version public var version: Version {
return xcodeID.version
}
public init?(path: Path) { public init?(path: Path) {
self.path = path self.path = path
@ -31,12 +36,21 @@ public struct InstalledXcode: Equatable {
else if infoPlist.bundleIconName == "XcodeBeta", !prereleaseIdentifiers.contains("beta") { else if infoPlist.bundleIconName == "XcodeBeta", !prereleaseIdentifiers.contains("beta") {
prereleaseIdentifiers = ["beta"] prereleaseIdentifiers = ["beta"]
} }
self.version = Version(major: bundleVersion.major, let archsString = try? XcodesKit.Current.shell.archs(path.url.appending(path: "Contents/MacOS/Xcode")).out
let architectures = archsString?
.trimmingCharacters(in: .whitespacesAndNewlines)
.split(separator: " ")
.compactMap { Architecture(rawValue: String($0)) }
let version = Version(major: bundleVersion.major,
minor: bundleVersion.minor, minor: bundleVersion.minor,
patch: bundleVersion.patch, patch: bundleVersion.patch,
prereleaseIdentifiers: prereleaseIdentifiers, prereleaseIdentifiers: prereleaseIdentifiers,
buildMetadataIdentifiers: [versionPlist.productBuildVersion].compactMap { $0 }) buildMetadataIdentifiers: [versionPlist.productBuildVersion].compactMap { $0 })
self.xcodeID = XcodeID(version: version, architectures: architectures)
} }
} }

View file

@ -70,5 +70,38 @@ extension Progress {
} }
} }
func updateFromXcodebuild(text: String) {
self.totalUnitCount = 100
self.completedUnitCount = 0
self.localizedAdditionalDescription = "" // to not show the addtional
do {
let downloadPattern = #"(\d+\.\d+)% \(([\d.]+ (?:MB|GB)) of ([\d.]+ GB)\)"#
let downloadRegex = try NSRegularExpression(pattern: downloadPattern)
// Search for matches in the text
if let match = downloadRegex.firstMatch(in: text, range: NSRange(text.startIndex..., in: text)) {
// Extract the percentage - simpler then trying to extract size MB/GB and convert to bytes.
if let percentRange = Range(match.range(at: 1), in: text), let percentDouble = Double(text[percentRange]) {
let percent = Int64(percentDouble.rounded())
self.completedUnitCount = percent
}
}
// "Downloading tvOS 18.1 Simulator (22J5567a): Installing..." or
// "Downloading tvOS 18.1 Simulator (22J5567a): Installing (registering download)..."
if text.range(of: "Installing") != nil {
// sets the progress to indeterminite to show animating progress
self.totalUnitCount = 0
self.completedUnitCount = 0
}
} catch {
Logger.appState.error("Invalid regular expression")
}
}
} }

View file

@ -7,7 +7,6 @@
// //
import Foundation import Foundation
import struct XCModel.SDKs
import XcodesKit import XcodesKit
import SwiftUI import SwiftUI

View file

@ -1,11 +1,11 @@
import Version import Version
import struct XCModel.Xcode import XcodesKit
extension Version { extension Version {
/// Initialize a Version from an XcodeReleases' XCModel.Xcode /// Initialize a Version from an XcodeReleases' XCModel.Xcode
/// ///
/// This is kinda quick-and-dirty, and it would probably be better for us to adopt something closer to XCModel.Xcode under the hood and map the scraped data to it instead. /// This is kinda quick-and-dirty, and it would probably be better for us to adopt something closer to XCModel.Xcode under the hood and map the scraped data to it instead.
init?(xcReleasesXcode: XCModel.Xcode) { init?(xcReleasesXcode: XcodeRelease) {
var versionString = xcReleasesXcode.version.number ?? "" var versionString = xcReleasesXcode.version.number ?? ""
// Append trailing ".0" in order to get a fully-specified version string // Append trailing ".0" in order to get a fully-specified version string

View file

@ -1,14 +1,30 @@
import AppKit import AppKit
import Foundation import Foundation
import Version import Version
import struct XCModel.SDKs
import struct XCModel.Compilers
import Path import Path
import XcodesKit
public struct XcodeID: Codable, Hashable, Identifiable {
public let version: Version
public let architectures: [Architecture]?
public var id: String {
let architectures = architectures?.map { $0.rawValue}.joined() ?? ""
return version.description + architectures
}
public init(version: Version, architectures: [Architecture]? = nil) {
self.version = version
self.architectures = architectures
}
}
struct Xcode: Identifiable, CustomStringConvertible { struct Xcode: Identifiable, CustomStringConvertible {
let version: Version var version: Version {
return id.version
}
/// Other Xcode versions that have the same build identifier /// Other Xcode versions that have the same build identifier
let identicalBuilds: [Version] let identicalBuilds: [XcodeID]
var installState: XcodeInstallState var installState: XcodeInstallState
let selected: Bool let selected: Bool
let icon: NSImage? let icon: NSImage?
@ -18,10 +34,12 @@ struct Xcode: Identifiable, CustomStringConvertible {
let sdks: SDKs? let sdks: SDKs?
let compilers: Compilers? let compilers: Compilers?
let downloadFileSize: Int64? let downloadFileSize: Int64?
let architectures: [Architecture]?
let id: XcodeID
init( init(
version: Version, version: Version,
identicalBuilds: [Version] = [], identicalBuilds: [XcodeID] = [],
installState: XcodeInstallState, installState: XcodeInstallState,
selected: Bool, selected: Bool,
icon: NSImage?, icon: NSImage?,
@ -30,9 +48,9 @@ struct Xcode: Identifiable, CustomStringConvertible {
releaseDate: Date? = nil, releaseDate: Date? = nil,
sdks: SDKs? = nil, sdks: SDKs? = nil,
compilers: Compilers? = nil, compilers: Compilers? = nil,
downloadFileSize: Int64? = nil downloadFileSize: Int64? = nil,
architectures: [Architecture]? = nil
) { ) {
self.version = version
self.identicalBuilds = identicalBuilds self.identicalBuilds = identicalBuilds
self.installState = installState self.installState = installState
self.selected = selected self.selected = selected
@ -43,10 +61,10 @@ struct Xcode: Identifiable, CustomStringConvertible {
self.sdks = sdks self.sdks = sdks
self.compilers = compilers self.compilers = compilers
self.downloadFileSize = downloadFileSize self.downloadFileSize = downloadFileSize
self.architectures = architectures
self.id = XcodeID(version: version, architectures: architectures)
} }
var id: Version { version }
var description: String { var description: String {
version.appleDescription version.appleDescription
} }

View file

@ -35,12 +35,11 @@ struct XcodeCommands: Commands {
struct InstallButton: View { struct InstallButton: View {
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
@State private var isLoading = false
let xcode: Xcode? let xcode: Xcode?
var body: some View { var body: some View {
ProgressButton(isInProgress: isLoading) { Button {
install() install()
} label: { } label: {
Text("Install") Text("Install")
@ -49,7 +48,6 @@ struct InstallButton: View {
} }
private func install() { private func install() {
isLoading = true
guard let xcode = xcode else { return } guard let xcode = xcode else { return }
appState.checkMinVersionAndInstall(id: xcode.id) appState.checkMinVersionAndInstall(id: xcode.id)
} }
@ -61,7 +59,7 @@ struct CancelInstallButton: View {
var body: some View { var body: some View {
Button(action: cancelInstall) { Button(action: cancelInstall) {
Image(systemName: "xmark.circle.fill") Label("Cancel", systemImage: "xmark")
} }
.help(localizeString("StopInstallation")) .help(localizeString("StopInstallation"))
.buttonStyle(.plain) .buttonStyle(.plain)

View file

@ -53,7 +53,7 @@ struct AboutView: View {
}) { }) {
HStack { HStack {
Image(systemName: "heart.circle") Image(systemName: "heart.circle")
Text("SponsorXcodes") Text("Support.Xcodes")
} }
} }
} }

View file

@ -10,7 +10,7 @@ struct AcknowledgmentsView: View {
)! )!
.addingAttribute(.foregroundColor, value: NSColor.labelColor) .addingAttribute(.foregroundColor, value: NSColor.labelColor)
) )
.frame(minWidth: 500, minHeight: 500) .frame(minWidth: 600, minHeight: 500)
} }
} }

View file

@ -20,27 +20,16 @@ struct NavigationSplitViewWrapper<Sidebar, Detail>: View where Sidebar: View, De
} }
var body: some View { var body: some View {
if #available(iOS 16, macOS 13, tvOS 16, watchOS 9, visionOS 1, *) { NavigationSplitView {
// Use the latest API available if #available(macOS 14, *) {
NavigationSplitView { sidebar
.navigationSplitViewColumnWidth(min: 290, ideal: 290)
if #available(macOS 14, *) { } else {
sidebar
.toolbar(removing: .sidebarToggle)
} else {
sidebar
}
} detail: {
detail
}
} else {
// Alternative code for earlier versions of OS.
NavigationView {
// The first column is the sidebar.
sidebar sidebar
detail
} }
.navigationViewStyle(.columns) } detail: {
detail
} }
.navigationSplitViewStyle(.balanced)
} }
} }

View file

@ -31,6 +31,7 @@ public struct ObservingProgressIndicator: View {
self.progress = progress self.progress = progress
cancellable = progress.publisher(for: \.fractionCompleted) cancellable = progress.publisher(for: \.fractionCompleted)
.combineLatest(progress.publisher(for: \.localizedAdditionalDescription)) .combineLatest(progress.publisher(for: \.localizedAdditionalDescription))
.combineLatest(progress.publisher(for: \.isIndeterminate))
.throttle(for: 1.0, scheduler: DispatchQueue.main, latest: true) .throttle(for: 1.0, scheduler: DispatchQueue.main, latest: true)
.sink { [weak self] _ in self?.objectWillChange.send() } .sink { [weak self] _ in self?.objectWillChange.send() }
} }
@ -82,6 +83,18 @@ struct ObservingProgressBar_Previews: PreviewProvider {
style: .bar, style: .bar,
showsAdditionalDescription: true showsAdditionalDescription: true
) )
ObservingProgressIndicator(
configure(Progress()) {
$0.kind = .file
$0.fileOperationKind = .downloading
$0.totalUnitCount = 0
$0.completedUnitCount = 0
},
controlSize: .regular,
style: .bar,
showsAdditionalDescription: true
)
} }
.previewLayout(.sizeThatFits) .previewLayout(.sizeThatFits)
} }

View file

@ -22,7 +22,10 @@ struct ProgressIndicator: NSViewRepresentable {
nsView.doubleValue = doubleValue nsView.doubleValue = doubleValue
nsView.controlSize = controlSize nsView.controlSize = controlSize
nsView.isIndeterminate = isIndeterminate nsView.isIndeterminate = isIndeterminate
nsView.usesThreadedAnimation = true
nsView.style = style nsView.style = style
nsView.startAnimation(nil)
} }
} }

View file

@ -0,0 +1,24 @@
//
// TagView.swift
// Xcodes
//
// Created by Matt Kiazyk on 2025-06-25.//
import SwiftUI
struct TagView: View {
let text: String
var body: some View {
Text(text)
.font(.system(size: 10))
.foregroundColor(.primary)
.padding(.horizontal, 5)
.padding(.vertical, 2)
.background(
Capsule()
.fill(.quaternary)
)
}
}

View file

@ -0,0 +1,22 @@
//
// TrailingIconLabelStyle.swift
// Xcodes
//
// Created by Daniel Chick on 3/11/24.
// Copyright © 2024 Robots and Pencils. All rights reserved.
//
import SwiftUI
struct TrailingIconLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.title
configuration.icon
}
}
}
extension LabelStyle where Self == TrailingIconLabelStyle {
static var trailingIcon: Self { Self() }
}

View file

@ -7,6 +7,7 @@ enum XcodesAlert: Identifiable {
case privilegedHelper case privilegedHelper
case generic(title: String, message: String) case generic(title: String, message: String)
case checkMinSupportedVersion(xcode: AvailableXcode, macOS: String) case checkMinSupportedVersion(xcode: AvailableXcode, macOS: String)
case unauthenticated
var id: Int { var id: Int {
switch self { switch self {
@ -15,6 +16,7 @@ enum XcodesAlert: Identifiable {
case .generic: return 3 case .generic: return 3
case .checkMinSupportedVersion: return 4 case .checkMinSupportedVersion: return 4
case .cancelRuntimeInstall: return 5 case .cancelRuntimeInstall: return 5
case .unauthenticated: return 6
} }
} }
} }

View file

@ -4,6 +4,7 @@ import AppleAPI
enum XcodesSheet: Identifiable { enum XcodesSheet: Identifiable {
case signIn case signIn
case twoFactor(SecondFactorData) case twoFactor(SecondFactorData)
case securityKeyTouchToConfirm
var id: Int { Kind(self).hashValue } var id: Int { Kind(self).hashValue }
@ -16,12 +17,13 @@ enum XcodesSheet: Identifiable {
extension XcodesSheet { extension XcodesSheet {
private enum Kind: Hashable { private enum Kind: Hashable {
case signIn, twoFactor(TwoFactorOption) case signIn, twoFactor(TwoFactorOption), securityKeyTouchToConfirm
enum TwoFactorOption { enum TwoFactorOption {
case smsSent case smsSent
case codeSent case codeSent
case smsPendingChoice case smsPendingChoice
case securityKeyPin
} }
init(_ sheet: XcodesSheet) { init(_ sheet: XcodesSheet) {
@ -32,7 +34,9 @@ extension XcodesSheet {
case .smsSent: self = .twoFactor(.smsSent) case .smsSent: self = .twoFactor(.smsSent)
case .smsPendingChoice: self = .twoFactor(.smsPendingChoice) case .smsPendingChoice: self = .twoFactor(.smsPendingChoice)
case .codeSent: self = .twoFactor(.codeSent) case .codeSent: self = .twoFactor(.codeSent)
case .securityKey: self = .twoFactor(.securityKeyPin)
} }
case .securityKeyTouchToConfirm: self = .securityKeyTouchToConfirm
} }
} }
} }

View file

@ -7,7 +7,7 @@
// //
import SwiftUI import SwiftUI
import struct XCModel.Compilers import XcodesKit
struct CompilersView: View { struct CompilersView: View {
let compilers: Compilers? let compilers: Compilers?
@ -16,7 +16,9 @@ struct CompilersView: View {
if let compilers = compilers { if let compilers = compilers {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text("Compilers").font(.headline) Text("Compilers").font(.headline)
Text(Self.content(from: compilers)).font(.subheadline) Text(Self.content(from: compilers))
.font(.subheadline)
.textSelection(.enabled)
} }
} else { } else {
EmptyView() EmptyView()

View file

@ -29,7 +29,7 @@ extension View {
struct Previews_CornerRadius_Previews: PreviewProvider { struct Previews_CornerRadius_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
HStack { HStack {
Text("XCODES RULES!") Text(verbatim: "XCODES RULES!")
}.xcodesBackground() }.xcodesBackground()
} }
} }

View file

@ -6,17 +6,18 @@
// Copyright © 2023 Robots and Pencils. All rights reserved. // Copyright © 2023 Robots and Pencils. All rights reserved.
// //
import SwiftUI
import Path import Path
import SwiftUI
import Version
struct IconView: View { struct IconView: View {
let installState: XcodeInstallState let xcode: Xcode
var body: some View { var body: some View {
if case let .installed(path) = installState { if case let .installed(path) = xcode.installState {
Image(nsImage: NSWorkspace.shared.icon(forFile: path.string)) Image(nsImage: NSWorkspace.shared.icon(forFile: path.string))
} else { } else {
Image(systemName: "app.fill") Image(xcode.version.isPrerelease ? "xcode-beta" : "xcode")
.resizable() .resizable()
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
.foregroundColor(.secondary) .foregroundColor(.secondary)
@ -25,13 +26,19 @@ struct IconView: View {
} }
#Preview("Installed") { #Preview("Installed") {
IconView(installState: XcodeInstallState.installed(Path("/Applications/Xcode.app")!)) IconView(xcode: Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil))
.frame(width: 300, height: 100) .frame(width: 300, height: 100)
.padding() .padding()
}
#Preview("Installed") {
IconView(xcode: Xcode(version: Version("12.3.0")!, installState: .notInstalled, selected: true, icon: nil))
.frame(width: 300, height: 100)
.padding()
} }
#Preview("Not Installed") { #Preview("Not Installed") {
IconView(installState: XcodeInstallState.notInstalled) IconView(xcode: Xcode(version: Version("12.0.0-1234A")!, installState: .notInstalled, selected: false, icon: nil))
.frame(width: 300, height: 100) .frame(width: 300, height: 100)
.padding() .padding()
} }

View file

@ -29,7 +29,7 @@ struct IdenticalBuildsView: View {
.font(.headline) .font(.headline)
ForEach(builds, id: \.description) { version in ForEach(builds, id: \.description) { version in
Text("\(version.appleDescription)") Text(verbatim: "\(version.appleDescription)")
.font(.subheadline) .font(.subheadline)
} }
} }

View file

@ -3,41 +3,44 @@ import XcodesKit
import Path import Path
import SwiftUI import SwiftUI
import Version import Version
import struct XCModel.Compilers
import struct XCModel.SDKs
struct InfoPane: View { struct InfoPane: View {
let xcode: Xcode let xcode: Xcode
var body: some View { var body: some View {
if #available(macOS 14.0, *) {
mainContent
.contentMargins(10, for: .scrollContent)
} else {
mainContent
.padding()
}
}
private var mainContent: some View {
ScrollView(.vertical) { ScrollView(.vertical) {
HStack(alignment: .top) { HStack(alignment: .top) {
VStack { VStack {
VStack(spacing: 5) { VStack(spacing: 5) {
HStack { HStack {
IconView(installState: xcode.installState) IconView(xcode: xcode)
Text(verbatim: "Xcode \(xcode.description) \(xcode.version.buildMetadataIdentifiersDisplay)") Text(verbatim: "Xcode \(xcode.description) \(xcode.version.buildMetadataIdentifiersDisplay)")
.font(.title) .font(.title)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.textSelection(.enabled)
} }
InfoPaneControls(xcode: xcode) InfoPaneControls(xcode: xcode)
} }
.xcodesBackground() .xcodesBackground()
PlatformsView(xcode: xcode)
VStack {
Text("Platforms")
.font(.title3)
.frame(maxWidth: .infinity, alignment: .leading)
PlatformsView(xcode: xcode)
}
.xcodesBackground()
} }
.frame(minWidth: 380)
VStack(alignment: .leading) { VStack(alignment: .leading) {
ReleaseDateView(date: xcode.releaseDate, url: xcode.releaseNotesURL) ReleaseDateView(date: xcode.releaseDate, url: xcode.releaseNotesURL)
CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion) CompatibilityView(requiredMacOSVersion: xcode.requiredMacOSVersion)
IdenticalBuildsView(builds: xcode.identicalBuilds) IdenticalBuildsView(builds: xcode.identicalBuilds.map { $0.version })
SDKandCompilers SDKandCompilers
} }
.frame(width: 200) .frame(width: 200)
@ -64,15 +67,16 @@ struct InfoPane: View {
#Preview(XcodePreviewName.allCases[2].rawValue) { makePreviewContent(for: 2) } #Preview(XcodePreviewName.allCases[2].rawValue) { makePreviewContent(for: 2) }
#Preview(XcodePreviewName.allCases[3].rawValue) { makePreviewContent(for: 3) } #Preview(XcodePreviewName.allCases[3].rawValue) { makePreviewContent(for: 3) }
#Preview(XcodePreviewName.allCases[4].rawValue) { makePreviewContent(for: 4) } #Preview(XcodePreviewName.allCases[4].rawValue) { makePreviewContent(for: 4) }
#Preview(XcodePreviewName.allCases[5].rawValue) { makePreviewContent(for: 5) }
private func makePreviewContent(for index: Int) -> some View { private func makePreviewContent(for index: Int) -> some View {
let name = XcodePreviewName.allCases[index] let name = XcodePreviewName.allCases[index]
return InfoPane(xcode: xcodeDict[name]!) return InfoPane(xcode: xcodeDict[name]!)
.environmentObject(configure(AppState()) { .environmentObject(configure(AppState()) {
$0.allXcodes = [xcodeDict[name]!] $0.allXcodes = [xcodeDict[name]!]
}) })
.frame(width: 300, height: 400) .frame(width: 600, height: 400)
.padding() .padding()
} }
enum XcodePreviewName: String, CaseIterable, Identifiable { enum XcodePreviewName: String, CaseIterable, Identifiable {
@ -81,7 +85,8 @@ enum XcodePreviewName: String, CaseIterable, Identifiable {
case Populated_Uninstalled case Populated_Uninstalled
case Basic_Installed case Basic_Installed
case Basic_Installing case Basic_Installing
case Basic_Unarchiving
var id: XcodePreviewName { self } var id: XcodePreviewName { self }
} }
@ -141,17 +146,25 @@ var xcodeDict: [XcodePreviewName: Xcode] = [
sdks: nil, sdks: nil,
compilers: nil compilers: nil
), ),
.Basic_Unarchiving: .init(
version: _versionWithMeta,
installState: .installing(.unarchiving),
selected: false,
icon: nil,
sdks: nil,
compilers: nil
),
] ]
var downloadableRuntimes: [DownloadableRuntime] = { var downloadableRuntimes: [DownloadableRuntime] = {
var runtimes = try! JSONDecoder().decode([DownloadableRuntime].self, from: Current.files.contents(atPath: Path.runtimeCacheFile.string)!) var runtimes = try! JSONDecoder().decode([DownloadableRuntime].self, from: Current.files.contents(atPath: Path.runtimeCacheFile.string)!)
// set iOS to installed // set iOS to installed
let iOSIndex = runtimes.firstIndex { $0.sdkBuildUpdate == "19E239" }! let iOSIndex = 0//runtimes.firstIndex { $0.sdkBuildUpdate.contains == "19E239" }!
var iOSRuntime = runtimes[iOSIndex] var iOSRuntime = runtimes[iOSIndex]
iOSRuntime.installState = .installed iOSRuntime.installState = .installed
runtimes[iOSIndex] = iOSRuntime runtimes[iOSIndex] = iOSRuntime
let watchOSIndex = runtimes.firstIndex { $0.sdkBuildUpdate == "20R362" }! let watchOSIndex = 0//runtimes.firstIndex { $0.sdkBuildUpdate.first == "20R362" }!
var runtime = runtimes[watchOSIndex] var runtime = runtimes[watchOSIndex]
runtime.installState = .installing( runtime.installState = .installing(
RuntimeInstallationStep.downloading( RuntimeInstallationStep.downloading(
@ -163,7 +176,7 @@ var downloadableRuntimes: [DownloadableRuntime] = {
$0.completedUnitCount = 848_444_920 $0.completedUnitCount = 848_444_920
$0.throughput = 9_211_681 $0.throughput = 9_211_681
} }
) )
) )
runtimes[watchOSIndex] = runtime runtimes[watchOSIndex] = runtime

View file

@ -25,6 +25,7 @@ struct InfoPaneControls: View {
case .installing(let installationStep): case .installing(let installationStep):
HStack(alignment: .top) { HStack(alignment: .top) {
InstallationStepDetailView(installationStep: installationStep) InstallationStepDetailView(installationStep: installationStep)
.frame(maxWidth: .infinity, alignment: .leading)
CancelInstallButton(xcode: xcode) CancelInstallButton(xcode: xcode)
} }
case .installed(_): case .installed(_):
@ -39,6 +40,7 @@ struct InfoPaneControls: View {
#Preview(XcodePreviewName.allCases[2].rawValue) { makePreviewContent(for: 2) } #Preview(XcodePreviewName.allCases[2].rawValue) { makePreviewContent(for: 2) }
#Preview(XcodePreviewName.allCases[3].rawValue) { makePreviewContent(for: 3) } #Preview(XcodePreviewName.allCases[3].rawValue) { makePreviewContent(for: 3) }
#Preview(XcodePreviewName.allCases[4].rawValue) { makePreviewContent(for: 4) } #Preview(XcodePreviewName.allCases[4].rawValue) { makePreviewContent(for: 4) }
#Preview(XcodePreviewName.allCases[5].rawValue) { makePreviewContent(for: 5) }
private func makePreviewContent(for index: Int) -> some View { private func makePreviewContent(for index: Int) -> some View {
let name = XcodePreviewName.allCases[index] let name = XcodePreviewName.allCases[index]
@ -47,6 +49,6 @@ private func makePreviewContent(for index: Int) -> some View {
.environmentObject(configure(AppState()) { .environmentObject(configure(AppState()) {
$0.allXcodes = [xcodeDict[name]!] $0.allXcodes = [xcodeDict[name]!]
}) })
.frame(width: 300) .frame(width: 500)
.padding() .padding()
} }

View file

@ -17,7 +17,7 @@ struct InstallationStepDetailView: View {
showsAdditionalDescription: true showsAdditionalDescription: true
) )
case .unarchiving, .moving, .trashingArchive, .checkingSecurity, .finishing: case .authenticating, .unarchiving, .moving, .trashingArchive, .checkingSecurity, .finishing:
ProgressView() ProgressView()
.scaleEffect(0.5) .scaleEffect(0.5)
} }

View file

@ -8,7 +8,7 @@
import SwiftUI import SwiftUI
import Version import Version
import XCModel import XcodesKit
import Path import Path
struct InstalledStateButtons: View { struct InstalledStateButtons: View {

View file

@ -11,7 +11,7 @@ import Version
struct NotInstalledStateButtons: View { struct NotInstalledStateButtons: View {
let downloadFileSizeString: String? let downloadFileSizeString: String?
let id: Version let id: XcodeID
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
@ -20,7 +20,11 @@ struct NotInstalledStateButtons: View {
Button { Button {
appState.checkMinVersionAndInstall(id: id) appState.checkMinVersionAndInstall(id: id)
} label: { } label: {
Text("Install") .help("Install") if id.architectures?.isAppleSilicon ?? false {
Text("Install Apple Silicon").help("Install")
} else {
Text("Install Universal").help("Install")
}
} }
if let size = downloadFileSizeString { if let size = downloadFileSizeString {
@ -38,7 +42,7 @@ struct NotInstalledStateButtons: View {
#Preview { #Preview {
NotInstalledStateButtons( NotInstalledStateButtons(
downloadFileSizeString: "1,19 GB", downloadFileSizeString: "1,19 GB",
id: Version(major: 12, minor: 3, patch: 0) id: XcodeID(version: Version(major: 12, minor: 3, patch: 0), architectures: nil)
) )
.padding() .padding()
} }

View file

@ -11,7 +11,8 @@ import XcodesKit
struct PlatformsView: View { struct PlatformsView: View {
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
@AppStorage("selectedRuntimeArchitecture") private var selectedVariant: ArchitectureVariant = .universal
let xcode: Xcode let xcode: Xcode
var body: some View { var body: some View {
@ -19,17 +20,49 @@ struct PlatformsView: View {
let builds = xcode.sdks?.allBuilds() let builds = xcode.sdks?.allBuilds()
let runtimes = builds?.flatMap { sdkBuild in let runtimes = builds?.flatMap { sdkBuild in
appState.downloadableRuntimes.filter { appState.downloadableRuntimes.filter {
$0.sdkBuildUpdate == sdkBuild $0.sdkBuildUpdate?.contains(sdkBuild) ?? false &&
($0.architectures?.isEmpty ?? true ||
($0.architectures?.isUniversal ?? false && selectedVariant == .universal) ||
($0.architectures?.isAppleSilicon ?? false && selectedVariant == .appleSilicon)
)
} }
} }
ForEach(runtimes ?? [], id: \.simulatorVersion.buildUpdate) { runtime in let architectures = Set((runtimes ?? []).flatMap { $0.architectures ?? [] })
runtimeView(runtime: runtime)
.frame(minWidth: 200) VStack {
.padding() HStack {
.background(.quinary) Text("Platforms")
.clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous)) .font(.title3)
.frame(maxWidth: .infinity, alignment: .leading)
if !architectures.isEmpty {
Spacer()
Picker("Architecture", selection: $selectedVariant) {
ForEach(ArchitectureVariant.allCases, id: \.self) { arch in
Label(arch.displayString, systemImage: arch.iconName)
.tag(arch)
}
.labelStyle(.trailingIcon)
}
.pickerStyle(.menu)
.menuStyle(.button)
.buttonStyle(.borderless)
.fixedSize()
.labelsHidden()
}
}
ForEach(runtimes ?? [], id: \.identifier) { runtime in
runtimeView(runtime: runtime)
.frame(minWidth: 200)
.padding()
.background(.quinary)
.clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous))
}
} }
.xcodesBackground()
} }
@ViewBuilder @ViewBuilder
@ -39,34 +72,38 @@ struct PlatformsView: View {
runtime.icon() runtime.icon()
Text("\(runtime.visibleIdentifier)") Text("\(runtime.visibleIdentifier)")
.font(.headline) .font(.headline)
ForEach(runtime.architectures ?? [], id: \.self) { architecture in
TagView(text: architecture.displayString)
}
pathIfAvailable(xcode: xcode, runtime: runtime) pathIfAvailable(xcode: xcode, runtime: runtime)
if runtime.installState == .notInstalled {
// TODO: Update the downloadableRuntimes with the appropriate installState so we don't have to check path awkwardly
if appState.runtimeInstallPath(xcode: xcode, runtime: runtime) != nil {
EmptyView()
} else {
HStack {
Spacer()
DownloadRuntimeButton(runtime: runtime)
}
}
}
Spacer() Spacer()
Text(runtime.downloadFileSizeString) Text(runtime.downloadFileSizeString)
.font(.subheadline) .font(.subheadline)
.frame(width: 70, alignment: .trailing)
} }
switch runtime.installState {
case .installed: if case let .installing(installationStep) = runtime.installState {
EmptyView() HStack(alignment: .top, spacing: 5){
case .notInstalled: RuntimeInstallationStepDetailView(installationStep: installationStep)
// TODO: Update the downloadableRuntimes with the appropriate installState so we don't have to check path awkwardly .fixedSize(horizontal: false, vertical: true)
if let path = appState.runtimeInstallPath(xcode: xcode, runtime: runtime) { Spacer()
EmptyView() CancelRuntimeInstallButton(runtime: runtime)
} else { }
HStack { }
Spacer()
DownloadRuntimeButton(runtime: runtime)
}
}
case .installing(let installationStep):
HStack(alignment: .top, spacing: 5){
RuntimeInstallationStepDetailView(installationStep: installationStep)
.fixedSize(horizontal: false, vertical: true)
Spacer()
CancelRuntimeInstallButton(runtime: runtime)
}
}
} }
} }

View file

@ -26,8 +26,12 @@ struct RuntimeInstallationStepDetailView: View {
) )
case .installing, .trashingArchive: case .installing, .trashingArchive:
ProgressView() ObservingProgressIndicator(
.scaleEffect(0.5) Progress(),
controlSize: .regular,
style: .bar,
showsAdditionalDescription: false
)
} }
} }
} }

View file

@ -7,7 +7,7 @@
// //
import SwiftUI import SwiftUI
import struct XCModel.SDKs import XcodesKit
struct SDKsView: View { struct SDKsView: View {
let content: String let content: String
@ -18,7 +18,9 @@ struct SDKsView: View {
} else { } else {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text("SDKs").font(.headline) Text("SDKs").font(.headline)
Text(content).font(.subheadline) Text(content)
.font(.subheadline)
.textSelection(.enabled)
} }
} }
} }

View file

@ -15,12 +15,12 @@ struct MainWindow: View {
// FB8979533 SceneStorage doesn't restore value after app is quit by user // FB8979533 SceneStorage doesn't restore value after app is quit by user
@AppStorage("isShowingInfoPane") private var isShowingInfoPane = false @AppStorage("isShowingInfoPane") private var isShowingInfoPane = false
@AppStorage("xcodeListCategory") private var category: XcodeListCategory = .all @AppStorage("xcodeListCategory") private var category: XcodeListCategory = .all
@AppStorage("xcodeListArchitecture") private var architecture: XcodeListArchitecture = .universal
@AppStorage("isInstalledOnly") private var isInstalledOnly = false @AppStorage("isInstalledOnly") private var isInstalledOnly = false
var body: some View { var body: some View {
NavigationSplitViewWrapper { NavigationSplitViewWrapper {
XcodeListView(selectedXcodeID: $selectedXcodeID, searchText: searchText, category: category, isInstalledOnly: isInstalledOnly) XcodeListView(selectedXcodeID: $selectedXcodeID, searchText: searchText, category: category, isInstalledOnly: isInstalledOnly, architecture: architecture)
.frame(minWidth: 250)
.layoutPriority(1) .layoutPriority(1)
.alert(item: $appState.xcodeBeingConfirmedForUninstallation) { xcode in .alert(item: $appState.xcodeBeingConfirmedForUninstallation) { xcode in
Alert(title: Text(String(format: localizeString("Alert.Uninstall.Title"), xcode.description)), Alert(title: Text(String(format: localizeString("Alert.Uninstall.Title"), xcode.description)),
@ -32,7 +32,8 @@ struct MainWindow: View {
.mainToolbar( .mainToolbar(
category: $category, category: $category,
isInstalledOnly: $isInstalledOnly, isInstalledOnly: $isInstalledOnly,
isShowingInfoPane: $isShowingInfoPane isShowingInfoPane: $isShowingInfoPane,
architecture: $architecture
) )
} detail: { } detail: {
Group { Group {
@ -42,7 +43,6 @@ struct MainWindow: View {
UnselectedView() UnselectedView()
} }
} }
.padding()
.toolbar { .toolbar {
ToolbarItemGroup { ToolbarItemGroup {
Button(action: { appState.presentedSheet = .signIn }, label: { Button(action: { appState.presentedSheet = .signIn }, label: {
@ -56,11 +56,7 @@ struct MainWindow: View {
.help("PreferencesDescription") .help("PreferencesDescription")
} else { } else {
Button(action: { Button(action: {
if #available(macOS 13, *) { NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
} else {
NSApp.sendAction(Selector(("showPreferencesWindow:")), to: nil, from: nil)
}
}, label: { }, label: {
Label("Preferences", systemImage: "gearshape") Label("Preferences", systemImage: "gearshape")
}) })
@ -82,6 +78,9 @@ struct MainWindow: View {
case .twoFactor(let secondFactorData): case .twoFactor(let secondFactorData):
secondFactorView(secondFactorData) secondFactorView(secondFactorData)
.environmentObject(appState) .environmentObject(appState)
case .securityKeyTouchToConfirm:
SignInSecurityKeyTouchView(isPresented: $appState.presentedSheet.isNotNil)
.environmentObject(appState)
} }
} }
.alert(item: $appState.presentedAlert, content: { presentedAlert in .alert(item: $appState.presentedAlert, content: { presentedAlert in
@ -113,6 +112,8 @@ struct MainWindow: View {
SignInSMSView(isPresented: $appState.presentedSheet.isNotNil, trustedPhoneNumber: trustedPhoneNumber, authOptions: secondFactorData.authOptions, sessionData: secondFactorData.sessionData) SignInSMSView(isPresented: $appState.presentedSheet.isNotNil, trustedPhoneNumber: trustedPhoneNumber, authOptions: secondFactorData.authOptions, sessionData: secondFactorData.sessionData)
case .smsPendingChoice: case .smsPendingChoice:
SignInPhoneListView(isPresented: $appState.presentedSheet.isNotNil, authOptions: secondFactorData.authOptions, sessionData: secondFactorData.sessionData) SignInPhoneListView(isPresented: $appState.presentedSheet.isNotNil, authOptions: secondFactorData.authOptions, sessionData: secondFactorData.sessionData)
case .securityKey:
SignInSecurityKeyPinView(isPresented: $appState.presentedSheet.isNotNil, authOptions: secondFactorData.authOptions, sessionData: secondFactorData.sessionData)
} }
} }
@ -189,14 +190,28 @@ struct MainWindow: View {
action: { appState.presentedAlert = nil } action: { appState.presentedAlert = nil }
) )
) )
case .unauthenticated:
return Alert(
title: Text("Alert.Install.Error.Title"),
message: Text("Alert.Install.AuthError.Message"),
primaryButton: .default(
Text("OK"),
action: {
appState.presentedSheet = .signIn
}
),
secondaryButton: .cancel(
Text("Cancel")
)
)
case let .checkMinSupportedVersion(xcode, deviceVersion): case let .checkMinSupportedVersion(xcode, deviceVersion):
return Alert( return Alert(
title: Text("Alert.MinSupported.Title"), title: Text("Alert.MinSupported.Title"),
message: Text(String(format: localizeString("Alert.MinSupported.Message"), xcode.version.descriptionWithoutBuildMetadata, xcode.requiredMacOSVersion ?? "", deviceVersion)), message: Text(String(format: localizeString("Alert.MinSupported.Message"), xcode.xcodeID.version.descriptionWithoutBuildMetadata, xcode.requiredMacOSVersion ?? "", deviceVersion)),
primaryButton: .default( primaryButton: .default(
Text("Install"), Text("Install"),
action: { action: {
self.appState.install(id: xcode.version) self.appState.install(id: xcode.xcodeID)
} }
), ),
secondaryButton: .cancel(Text("Cancel")) secondaryButton: .cancel(Text("Cancel"))
@ -224,7 +239,7 @@ struct MainWindow_Previews: PreviewProvider {
MainWindow().environmentObject({ () -> AppState in MainWindow().environmentObject({ () -> AppState in
let a = AppState() let a = AppState()
a.allXcodes = [ a.allXcodes = [
Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [Version("12.0.0+1234A")!, Version("12.0.0-RC+1234A")!], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil), Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [XcodeID(version: Version("12.0.0+1234A")!), XcodeID(version: Version("12.0.0-RC+1234A")!)], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil), Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil),
Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil), Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil),
Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil), Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil),

View file

@ -36,8 +36,10 @@ struct AdvancedPreferencePane: View {
self.appState.installPath = path.string self.appState.installPath = path.string
} }
} }
.disabled(appState.disableInstallPathChange)
Text("InstallPathDescription") Text("InstallPathDescription")
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
} }
} }
@ -71,8 +73,10 @@ struct AdvancedPreferencePane: View {
self.appState.localPath = path.string self.appState.localPath = path.string
} }
} }
.disabled(appState.disableLocalPathChange)
Text("LocalCachePathDescription") Text("LocalCachePathDescription")
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
} }
} }
@ -80,18 +84,22 @@ struct AdvancedPreferencePane: View {
GroupBox(label: Text("Active/Select")) { GroupBox(label: Text("Active/Select")) {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Picker("OnSelect", selection: $appState.onSelectActionType) { Picker(selection: $appState.onSelectActionType) {
Text(SelectedActionType.none.description) Text(SelectedActionType.none.description)
.tag(SelectedActionType.none) .tag(SelectedActionType.none)
Text(SelectedActionType.rename.description) Text(SelectedActionType.rename.description)
.tag(SelectedActionType.rename) .tag(SelectedActionType.rename)
} label: {
Text(verbatim: "OnSelect")
} }
.labelsHidden() .labelsHidden()
.pickerStyle(.inline) .pickerStyle(.inline)
.disabled(appState.onSelectActionTypeDisabled)
Text(appState.onSelectActionType.detailedDescription) Text(appState.onSelectActionType.detailedDescription)
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
Spacer() Spacer()
.frame(height: 20) .frame(height: 20)
@ -100,6 +108,7 @@ struct AdvancedPreferencePane: View {
.disabled(appState.createSymLinkOnSelectDisabled) .disabled(appState.createSymLinkOnSelectDisabled)
Text("AutomaticallyCreateSymbolicLinkDescription") Text("AutomaticallyCreateSymbolicLinkDescription")
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
} }
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
@ -112,6 +121,7 @@ struct AdvancedPreferencePane: View {
.disabled(appState.createSymLinkOnSelectDisabled) .disabled(appState.createSymLinkOnSelectDisabled)
Text("ShowOpenInRosettaDescription") Text("ShowOpenInRosettaDescription")
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
} }
.groupBoxStyle(PreferencesGroupBoxStyle()) .groupBoxStyle(PreferencesGroupBoxStyle())
@ -126,16 +136,18 @@ struct AdvancedPreferencePane: View {
case .installed: case .installed:
Text("HelperInstalled") Text("HelperInstalled")
case .notInstalled: case .notInstalled:
HStack { VStack(alignment: .leading) {
Text("HelperNotInstalled")
Button("InstallHelper") { Button("InstallHelper") {
appState.installHelperIfNecessary() appState.installHelperIfNecessary()
} }
Text("HelperNotInstalled")
.font(.footnote)
} }
} }
Text("PrivilegedHelperDescription") Text("PrivilegedHelperDescription")
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
Spacer() Spacer()
@ -151,9 +163,9 @@ struct AdvancedPreferencePane_Previews: PreviewProvider {
Group { Group {
AdvancedPreferencePane() AdvancedPreferencePane()
.environmentObject(AppState()) .environmentObject(AppState())
.frame(maxWidth: 500) .frame(maxWidth: 600)
} }
.frame(width: 500, height: 700, alignment: .center) .frame(width: 600, height: 700, alignment: .center)
} }
} }
@ -161,11 +173,8 @@ struct AdvancedPreferencePane_Previews: PreviewProvider {
struct PreferencesGroupBoxStyle: GroupBoxStyle { struct PreferencesGroupBoxStyle: GroupBoxStyle {
func makeBody(configuration: Configuration) -> some View { func makeBody(configuration: Configuration) -> some View {
HStack(alignment: .top, spacing: 20) { HStack(alignment: .top, spacing: 20) {
HStack { configuration.label
Spacer() .frame(width: 180, alignment: .trailing)
configuration.label
}
.frame(width: 120)
VStack(alignment: .leading) { VStack(alignment: .leading) {
configuration.content configuration.content

View file

@ -18,13 +18,17 @@ struct DownloadPreferencePane: View {
} }
} }
.labelsHidden() .labelsHidden()
.fixedSize()
AttributedText(dataSourceFootnote)
Text("DataSourceDescription")
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
} }
} }
.groupBoxStyle(PreferencesGroupBoxStyle()) .groupBoxStyle(PreferencesGroupBoxStyle())
.disabled(dataSource.isManaged)
GroupBox(label: Text("Downloader")) { GroupBox(label: Text("Downloader")) {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Picker("Downloader", selection: $downloader) { Picker("Downloader", selection: $downloader) {
@ -34,49 +38,27 @@ struct DownloadPreferencePane: View {
} }
} }
.labelsHidden() .labelsHidden()
.fixedSize()
AttributedText(downloaderFootnote)
Text("DownloaderDescription")
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
} }
} }
.groupBoxStyle(PreferencesGroupBoxStyle()) .groupBoxStyle(PreferencesGroupBoxStyle())
.disabled(downloader.isManaged)
} }
} }
private var dataSourceFootnote: NSAttributedString {
let string = localizeString("DataSourceDescription")
let attributedString = NSMutableAttributedString(
string: string,
attributes: [
.font: NSFont.preferredFont(forTextStyle: .footnote, options: [:]),
.foregroundColor: NSColor.labelColor
]
)
attributedString.addAttribute(.link, value: URL(string: "https://xcodereleases.com")!, range: NSRange(string.range(of: "Xcode Releases")!, in: string))
return attributedString
}
private var downloaderFootnote: NSAttributedString {
let string = localizeString("DownloaderDescription")
let attributedString = NSMutableAttributedString(
string: string,
attributes: [
.font: NSFont.preferredFont(forTextStyle: .footnote, options: [:]),
.foregroundColor: NSColor.labelColor
]
)
attributedString.addAttribute(.link, value: URL(string: "https://github.com/aria2/aria2")!, range: NSRange(string.range(of: "aria2")!, in: string))
return attributedString
}
} }
struct DownloadPreferencePane_Previews: PreviewProvider { struct DownloadPreferencePane_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
Group { Group {
GeneralPreferencePane() DownloadPreferencePane()
.environmentObject(AppState()) .environmentObject(AppState())
.frame(maxWidth: 500) .frame(maxWidth: 600)
.frame(minHeight: 300)
} }
} }
} }

View file

@ -1,6 +1,6 @@
import AppleAPI import AppleAPI
import SwiftUI
import Path import Path
import SwiftUI
struct ExperimentsPreferencePane: View { struct ExperimentsPreferencePane: View {
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
@ -13,29 +13,17 @@ struct ExperimentsPreferencePane: View {
"UseUnxipExperiment", "UseUnxipExperiment",
isOn: $appState.unxipExperiment isOn: $appState.unxipExperiment
) )
AttributedText(unxipFootnote) .disabled(appState.disableUnxipExperiment)
Text("FasterUnxipDescription")
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
} }
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
} }
.groupBoxStyle(PreferencesGroupBoxStyle()) .groupBoxStyle(PreferencesGroupBoxStyle())
Divider()
} }
} }
private var unxipFootnote: NSAttributedString {
let string = localizeString("FasterUnxipDescription")
let attributedString = NSMutableAttributedString(
string: string,
attributes: [
.font: NSFont.preferredFont(forTextStyle: .footnote, options: [:]),
.foregroundColor: NSColor.labelColor
]
)
attributedString.addAttribute(.link, value: URL(string: "https://twitter.com/_saagarjha")!, range: NSRange(string.range(of: "@_saagarjha")!, in: string))
attributedString.addAttribute(.link, value: URL(string: "https://github.com/saagarjha/unxip")!, range: NSRange(string.range(of: "https://github.com/saagarjha/unxip")!, in: string))
return attributedString
}
} }
struct ExperimentsPreferencePane_Previews: PreviewProvider { struct ExperimentsPreferencePane_Previews: PreviewProvider {
@ -43,7 +31,7 @@ struct ExperimentsPreferencePane_Previews: PreviewProvider {
Group { Group {
ExperimentsPreferencePane() ExperimentsPreferencePane()
.environmentObject(AppState()) .environmentObject(AppState())
.frame(maxWidth: 500) .frame(maxWidth: 600)
} }
} }
} }

View file

@ -20,6 +20,12 @@ struct GeneralPreferencePane: View {
NotificationsView().environmentObject(appState) NotificationsView().environmentObject(appState)
} }
.groupBoxStyle(PreferencesGroupBoxStyle()) .groupBoxStyle(PreferencesGroupBoxStyle())
Divider()
GroupBox(label: Text("Misc")) {
Toggle("TerminateAfterLastWindowClosed", isOn: $appState.terminateAfterLastWindowClosed)
}
.groupBoxStyle(PreferencesGroupBoxStyle())
} }
} }
} }
@ -29,7 +35,7 @@ struct GeneralPreferencePane_Previews: PreviewProvider {
Group { Group {
GeneralPreferencePane() GeneralPreferencePane()
.environmentObject(AppState()) .environmentObject(AppState())
.frame(maxWidth: 500) .frame(maxWidth: 600)
} }
} }
} }

View file

@ -39,6 +39,6 @@ struct PreferencesView: View {
.tag(Tabs.experiment) .tag(Tabs.experiment)
} }
.padding(20) .padding(20)
.frame(width: 500) .frame(width: 600)
} }
} }

View file

@ -15,11 +15,13 @@ struct UpdatesPreferencePane: View {
"AutomaticInstallNewVersion", "AutomaticInstallNewVersion",
isOn: $autoInstallationType.isAutoInstalling isOn: $autoInstallationType.isAutoInstalling
) )
.disabled(updater.disableAutoInstallNewVersions)
Toggle( Toggle(
"IncludePreRelease", "IncludePreRelease",
isOn: $autoInstallationType.isAutoInstallingBeta isOn: $autoInstallationType.isAutoInstallingBeta
) )
.disabled(updater.disableIncludePrereleaseVersions)
} }
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
} }
@ -34,18 +36,23 @@ struct UpdatesPreferencePane: View {
isOn: $updater.automaticallyChecksForUpdates isOn: $updater.automaticallyChecksForUpdates
) )
.fixedSize(horizontal: true, vertical: false) .fixedSize(horizontal: true, vertical: false)
.disabled(updater.disableAutoUpdateXcodesApp)
Toggle( Toggle(
"IncludePreRelease", "IncludePreRelease",
isOn: $updater.includePrereleaseVersions isOn: $updater.includePrereleaseVersions
) )
.disabled(updater.disableAutoUpdateXcodesAppPrereleaseVersions)
Button("CheckNow") { Button("CheckNow") {
updater.checkForUpdates() updater.checkForUpdates()
} }
.padding(.top)
.disabled(updater.disableAutoUpdateXcodesApp)
Text(String(format: localizeString("LastChecked"), lastUpdatedString)) Text(String(format: localizeString("LastChecked"), lastUpdatedString))
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary)
} }
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
} }
@ -81,12 +88,18 @@ class ObservableUpdater: ObservableObject {
private var lastUpdateCheckDateObservation: NSKeyValueObservation? private var lastUpdateCheckDateObservation: NSKeyValueObservation?
@Published var includePrereleaseVersions = false { @Published var includePrereleaseVersions = false {
didSet { didSet {
UserDefaults.standard.setValue(includePrereleaseVersions, forKey: "includePrereleaseVersions") Current.defaults.set(includePrereleaseVersions, forKey: "includePrereleaseVersions")
updaterDelegate.includePrereleaseVersions = includePrereleaseVersions updaterDelegate.includePrereleaseVersions = includePrereleaseVersions
} }
} }
var disableAutoInstallNewVersions: Bool { PreferenceKey.autoInstallation.isManaged() }
var disableIncludePrereleaseVersions: Bool { PreferenceKey.autoInstallation.isManaged() }
var disableAutoUpdateXcodesApp: Bool { PreferenceKey.SUEnableAutomaticChecks.isManaged() }
var disableAutoUpdateXcodesAppPrereleaseVersions: Bool { PreferenceKey.includePrereleaseVersions.isManaged() }
init() { init() {
updater = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: updaterDelegate, userDriverDelegate: nil).updater updater = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: updaterDelegate, userDriverDelegate: nil).updater
@ -109,7 +122,7 @@ class ObservableUpdater: ObservableObject {
self.lastUpdateCheckDate = updater.lastUpdateCheckDate self.lastUpdateCheckDate = updater.lastUpdateCheckDate
} }
) )
includePrereleaseVersions = UserDefaults.standard.bool(forKey: "includePrereleaseVersions") includePrereleaseVersions = Current.defaults.bool(forKey: "includePrereleaseVersions") ?? false
} }
func checkForUpdates() { func checkForUpdates() {
@ -140,7 +153,9 @@ struct UpdatesPreferencePane_Previews: PreviewProvider {
Group { Group {
UpdatesPreferencePane() UpdatesPreferencePane()
.environmentObject(AppState()) .environmentObject(AppState())
.frame(maxWidth: 500) .environmentObject(ObservableUpdater())
.frame(maxWidth: 600)
.frame(minHeight: 300)
} }
} }
} }

View file

@ -10,12 +10,12 @@ struct SignIn2FAView: View {
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text(String(format: localizeString("DigitCodeDescription"), authOptions.securityCode.length)) Text(String(format: localizeString("DigitCodeDescription"), authOptions.securityCode!.length))
.fixedSize(horizontal: true, vertical: false) .fixedSize(horizontal: true, vertical: false)
HStack { HStack {
Spacer() Spacer()
PinCodeTextField(code: $code, numberOfDigits: authOptions.securityCode.length) { PinCodeTextField(code: $code, numberOfDigits: authOptions.securityCode!.length) {
appState.submitSecurityCode(.device(code: $0), sessionData: sessionData) appState.submitSecurityCode(.device(code: $0), sessionData: sessionData)
} }
Spacer() Spacer()
@ -32,7 +32,7 @@ struct SignIn2FAView: View {
Text("Continue") Text("Continue")
} }
.keyboardShortcut(.defaultAction) .keyboardShortcut(.defaultAction)
.disabled(code.count != authOptions.securityCode.length) .disabled(code.count != authOptions.securityCode!.length)
} }
.frame(height: 25) .frame(height: 25)
} }

View file

@ -1,9 +1,14 @@
import SwiftUI import SwiftUI
struct SignInCredentialsView: View { struct SignInCredentialsView: View {
private enum FocusedField {
case username, password
}
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
@State private var username: String = "" @State private var username: String = ""
@State private var password: String = "" @State private var password: String = ""
@FocusState private var focusedField: FocusedField?
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
@ -13,12 +18,16 @@ struct SignInCredentialsView: View {
HStack { HStack {
Text("AppleID") Text("AppleID")
.frame(minWidth: 100, alignment: .trailing) .frame(minWidth: 100, alignment: .trailing)
TextField("example@icloud.com", text: $username) TextField(text: $username) {
Text(verbatim: "example@icloud.com")
}
.focused($focusedField, equals: .username)
} }
HStack { HStack {
Text("Password") Text("Password")
.frame(minWidth: 100, alignment: .trailing) .frame(minWidth: 100, alignment: .trailing)
SecureField("Required", text: $password) SecureField("Required", text: $password)
.focused($focusedField, equals: .password)
} }
if appState.authError != nil { if appState.authError != nil {
HStack { HStack {

View file

@ -1,5 +1,5 @@
import SwiftUI
import AppleAPI import AppleAPI
import SwiftUI
struct SignInPhoneListView: View { struct SignInPhoneListView: View {
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
@ -7,12 +7,12 @@ struct SignInPhoneListView: View {
@State private var selectedPhoneNumberID: AuthOptionsResponse.TrustedPhoneNumber.ID? @State private var selectedPhoneNumberID: AuthOptionsResponse.TrustedPhoneNumber.ID?
let authOptions: AuthOptionsResponse let authOptions: AuthOptionsResponse
let sessionData: AppleSessionData let sessionData: AppleSessionData
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if let phoneNumbers = authOptions.trustedPhoneNumbers, !phoneNumbers.isEmpty { if let phoneNumbers = authOptions.trustedPhoneNumbers, !phoneNumbers.isEmpty {
Text(String(format: localizeString("SelectTrustedPhone"), authOptions.securityCode.length)) Text(String(format: localizeString("SelectTrustedPhone"), authOptions.securityCode!.length))
List(phoneNumbers, selection: $selectedPhoneNumberID) { List(phoneNumbers, selection: $selectedPhoneNumberID) {
Text($0.numberWithDialCode) Text($0.numberWithDialCode)
} }
@ -22,19 +22,18 @@ struct SignInPhoneListView: View {
} }
} }
} else { } else {
AttributedText( Text("NoTrustedPhones")
NSAttributedString(string: localizeString("NoTrustedPhones")) .font(.callout)
.convertingURLsToLinkAttributes()
)
Spacer() Spacer()
} }
HStack { HStack {
Button("Cancel", action: { isPresented = false }) Button("Cancel", action: { isPresented = false })
.keyboardShortcut(.cancelAction) .keyboardShortcut(.cancelAction)
Spacer() Spacer()
ProgressButton(isInProgress: appState.isProcessingAuthRequest, ProgressButton(isInProgress: appState.isProcessingAuthRequest,
action: { appState.requestSMS(to: authOptions.trustedPhoneNumbers!.first { $0.id == selectedPhoneNumberID }!, authOptions: authOptions, sessionData: sessionData) }) { action: { appState.requestSMS(to: authOptions.trustedPhoneNumbers!.first { $0.id == selectedPhoneNumberID }!, authOptions: authOptions, sessionData: sessionData) })
{
Text("Continue") Text("Continue")
} }
.keyboardShortcut(.defaultAction) .keyboardShortcut(.defaultAction)
@ -54,9 +53,10 @@ struct SignInPhoneListView_Previews: PreviewProvider {
SignInPhoneListView( SignInPhoneListView(
isPresented: .constant(true), isPresented: .constant(true),
authOptions: AuthOptionsResponse( authOptions: AuthOptionsResponse(
trustedPhoneNumbers: [.init(id: 0, numberWithDialCode: "(•••) •••-••90")], trustedPhoneNumbers: [.init(id: 0, numberWithDialCode: "(•••) •••-••90")],
trustedDevices: nil, trustedDevices: nil,
securityCode: .init(length: 6)), securityCode: .init(length: 6)
),
sessionData: AppleSessionData(serviceKey: "", sessionID: "", scnt: "") sessionData: AppleSessionData(serviceKey: "", sessionID: "", scnt: "")
) )
.environmentObject(AppState()) .environmentObject(AppState())
@ -64,9 +64,10 @@ struct SignInPhoneListView_Previews: PreviewProvider {
SignInPhoneListView( SignInPhoneListView(
isPresented: .constant(true), isPresented: .constant(true),
authOptions: AuthOptionsResponse( authOptions: AuthOptionsResponse(
trustedPhoneNumbers: [], trustedPhoneNumbers: [],
trustedDevices: nil, trustedDevices: nil,
securityCode: .init(length: 6)), securityCode: .init(length: 6)
),
sessionData: AppleSessionData(serviceKey: "", sessionID: "", scnt: "") sessionData: AppleSessionData(serviceKey: "", sessionID: "", scnt: "")
) )
.environmentObject(AppState()) .environmentObject(AppState())

View file

@ -11,11 +11,11 @@ struct SignInSMSView: View {
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text(String(format: localizeString("EnterDigitCodeDescription"), authOptions.securityCode.length, trustedPhoneNumber.numberWithDialCode)) Text(String(format: localizeString("EnterDigitCodeDescription"), authOptions.securityCode!.length, trustedPhoneNumber.numberWithDialCode))
HStack { HStack {
Spacer() Spacer()
PinCodeTextField(code: $code, numberOfDigits: authOptions.securityCode.length) { PinCodeTextField(code: $code, numberOfDigits: authOptions.securityCode!.length) {
appState.submitSecurityCode(.sms(code: $0, phoneNumberId: trustedPhoneNumber.id), sessionData: sessionData) appState.submitSecurityCode(.sms(code: $0, phoneNumberId: trustedPhoneNumber.id), sessionData: sessionData)
} }
Spacer() Spacer()
@ -31,7 +31,7 @@ struct SignInSMSView: View {
Text("Continue") Text("Continue")
} }
.keyboardShortcut(.defaultAction) .keyboardShortcut(.defaultAction)
.disabled(code.count != authOptions.securityCode.length) .disabled(code.count != authOptions.securityCode!.length)
} }
.frame(height: 25) .frame(height: 25)
} }

View file

@ -0,0 +1,70 @@
//
// SignInSecurityKeyPin.swift
// Xcodes
//
// Created by Kino on 2024-09-26.
// Copyright © 2024 Robots and Pencils. All rights reserved.
//
import SwiftUI
import AppleAPI
struct SignInSecurityKeyPinView: View {
@EnvironmentObject var appState: AppState
@Binding var isPresented: Bool
@State private var pin: String = ""
let authOptions: AuthOptionsResponse
let sessionData: AppleSessionData
var body: some View {
VStack(alignment: .leading) {
Text(localizeString("SecurityKeyPinDescription"))
.fixedSize(horizontal: true, vertical: false)
HStack {
Spacer()
SecureField("PIN", text: $pin)
Spacer()
}
.padding()
HStack {
Button("Cancel", action: { isPresented = false })
.keyboardShortcut(.cancelAction)
Spacer()
Button("PIN not set", action: submitWithoutPinCode)
ProgressButton(isInProgress: appState.isProcessingAuthRequest,
action: submitPinCode) {
Text("Continue")
}
.keyboardShortcut(.defaultAction)
// FIDO2 device pin codes must be at least 4 code points
// https://docs.yubico.com/yesdk/users-manual/application-fido2/fido2-pin.html
.disabled(pin.count < 4)
}
.frame(height: 25)
}
.padding()
.emittingError($appState.authError, recoveryHandler: { _ in })
}
func submitPinCode() {
appState.createAndSubmitSecurityKeyAssertationWithPinCode(pin, sessionData: sessionData, authOptions: authOptions)
}
func submitWithoutPinCode() {
appState.createAndSubmitSecurityKeyAssertationWithPinCode(nil, sessionData: sessionData, authOptions: authOptions)
}
}
#Preview {
SignInSecurityKeyPinView(isPresented: .constant(true),
authOptions: AuthOptionsResponse(
trustedPhoneNumbers: nil,
trustedDevices: nil,
securityCode: .init(length: 6)
), sessionData: AppleSessionData(serviceKey: "", sessionID: "", scnt: ""))
.environmentObject(AppState())
}

View file

@ -0,0 +1,54 @@
//
// SignInSecurityKeyPin.swift
// Xcodes
//
// Created by Kino on 2024-09-26.
// Copyright © 2024 Robots and Pencils. All rights reserved.
//
import SwiftUI
import AppleAPI
struct SignInSecurityKeyTouchView: View {
@EnvironmentObject var appState: AppState
@Binding var isPresented: Bool
var body: some View {
VStack(alignment: .center) {
Image(systemName: "key.radiowaves.forward")
.font(.system(size: 32)).bold()
.padding(.bottom)
HStack {
Spacer()
Text(localizeString("SecurityKeyTouchDescription"))
.fixedSize(horizontal: true, vertical: false)
Spacer()
}
HStack {
Button("Cancel", action: self.cancel)
.keyboardShortcut(.cancelAction)
Spacer()
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.scaleEffect(x: 0.5, y: 0.5, anchor: .center)
.isHidden(!appState.isProcessingAuthRequest)
.keyboardShortcut(.defaultAction)
}
.frame(height: 25)
}
.padding()
.emittingError($appState.authError, recoveryHandler: { _ in })
}
func cancel() {
appState.cancelSecurityKeyAssertationRequest()
isPresented = false
}
}
#Preview {
SignInSecurityKeyTouchView(isPresented: .constant(true))
.environmentObject(AppState())
}

View file

@ -14,10 +14,10 @@ extension View {
struct View_IsHidden_Previews: PreviewProvider { struct View_IsHidden_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
Group { Group {
Text("Not Hidden") Text(verbatim: "Not Hidden")
.isHidden(false) .isHidden(false)
Text("Hidden") Text(verbatim: "Hidden")
.isHidden(true) .isHidden(true)
} }
} }

View file

@ -97,33 +97,45 @@ struct AppStoreButtonStyle_Previews: PreviewProvider {
Group { Group {
ForEach([ColorScheme.light, .dark], id: \.self) { colorScheme in ForEach([ColorScheme.light, .dark], id: \.self) { colorScheme in
Group { Group {
Button("OPEN", action: {}) Button{ } label: {
Text(verbatim: "OPEN")
}
.buttonStyle(AppStoreButtonStyle(primary: true, highlighted: false)) .buttonStyle(AppStoreButtonStyle(primary: true, highlighted: false))
.padding() .padding()
.background(Color(.textBackgroundColor)) .background(Color(.textBackgroundColor))
.previewDisplayName("Primary") .previewDisplayName("Primary")
Button("OPEN", action: {}) Button{ } label: {
Text(verbatim: "OPEN")
}
.buttonStyle(AppStoreButtonStyle(primary: true, highlighted: true)) .buttonStyle(AppStoreButtonStyle(primary: true, highlighted: true))
.padding() .padding()
.background(Color(.controlAccentColor)) .background(Color(.controlAccentColor))
.previewDisplayName("Primary, Highlighted") .previewDisplayName("Primary, Highlighted")
Button("OPEN", action: {}) Button{ } label: {
Text(verbatim: "OPEN")
}
.buttonStyle(AppStoreButtonStyle(primary: true, highlighted: false)) .buttonStyle(AppStoreButtonStyle(primary: true, highlighted: false))
.padding() .padding()
.disabled(true) .disabled(true)
.background(Color(.textBackgroundColor)) .background(Color(.textBackgroundColor))
.previewDisplayName("Primary, Disabled") .previewDisplayName("Primary, Disabled")
Button("INSTALL", action: {}) Button{ } label: {
Text(verbatim: "INSTALL")
}
.buttonStyle(AppStoreButtonStyle(primary: false, highlighted: false)) .buttonStyle(AppStoreButtonStyle(primary: false, highlighted: false))
.padding() .padding()
.background(Color(.textBackgroundColor)) .background(Color(.textBackgroundColor))
.previewDisplayName("Secondary") .previewDisplayName("Secondary")
Button("INSTALL", action: {}) Button{ } label: {
Text(verbatim: "INSTALL")
}
.buttonStyle(AppStoreButtonStyle(primary: false, highlighted: true)) .buttonStyle(AppStoreButtonStyle(primary: false, highlighted: true))
.padding() .padding()
.background(Color(.controlAccentColor)) .background(Color(.controlAccentColor))
.previewDisplayName("Secondary, Highlighted") .previewDisplayName("Secondary, Highlighted")
Button("INSTALL", action: {}) Button{ } label: {
Text(verbatim: "INSTALL")
}
.buttonStyle(AppStoreButtonStyle(primary: false, highlighted: false)) .buttonStyle(AppStoreButtonStyle(primary: false, highlighted: false))
.padding() .padding()
.disabled(true) .disabled(true)

View file

@ -11,6 +11,8 @@ import SwiftUI
struct BottomStatusModifier: ViewModifier { struct BottomStatusModifier: ViewModifier {
@EnvironmentObject var appState: AppState @EnvironmentObject var appState: AppState
@AppStorage(PreferenceKey.hideSupportXcodes.rawValue) var hideSupportXcodes = false
@SwiftUI.Environment(\.openURL) var openURL: OpenURLAction @SwiftUI.Environment(\.openURL) var openURL: OpenURLAction
func body(content: Content) -> some View { func body(content: Content) -> some View {
@ -20,17 +22,19 @@ struct BottomStatusModifier: ViewModifier {
Divider() Divider()
HStack { HStack {
Text(appState.bottomStatusBarMessage) Text(appState.bottomStatusBarMessage)
.font(.subheadline) .font(.subheadline)
Spacer() Spacer()
Button(action: { if !hideSupportXcodes {
openURL(URL(string: "https://opencollective.com/xcodesapp")!) Button(action: {
}) { openURL(URL(string: "https://opencollective.com/xcodesapp")!)
HStack { }) {
Image(systemName: "heart.circle") HStack {
Text("SponsorXcodes") Image(systemName: "heart.circle")
Text("Support.Xcodes")
}
} }
} }
Text(Bundle.main.shortVersion!) Text(verbatim: "\(Bundle.main.shortVersion!) (\(Bundle.main.version!))")
.font(.subheadline) .font(.subheadline)
} }
.frame(maxWidth: .infinity, maxHeight: 30, alignment: .leading) .frame(maxWidth: .infinity, maxHeight: 30, alignment: .leading)
@ -51,8 +55,34 @@ extension View {
struct Previews_BottomStatusBar_Previews: PreviewProvider { struct Previews_BottomStatusBar_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
HStack { Group {
HStack {
}.bottomStatusBar()
}
.bottomStatusBar()
.environmentObject({ () -> AppState in
let a = AppState()
return a }()
)
.defaultAppStorage({ () -> UserDefaults in
let d = UserDefaults(suiteName: "hide_support")!
d.set(true, forKey: PreferenceKey.hideSupportXcodes.rawValue)
return d
}())
HStack {
}
.bottomStatusBar()
.environmentObject({ () -> AppState in
let a = AppState()
return a }()
)
.defaultAppStorage({ () -> UserDefaults in
let d = UserDefaults(suiteName: "show_support")!
d.set(false, forKey: PreferenceKey.hideSupportXcodes.rawValue)
return d
}())
}
} }
} }

View file

@ -18,7 +18,7 @@ struct InstallationStepRowView: View {
controlSize: .small, controlSize: .small,
style: .spinning style: .spinning
) )
case .unarchiving, .moving, .trashingArchive, .checkingSecurity, .finishing: case .authenticating, .unarchiving, .moving, .trashingArchive, .checkingSecurity, .finishing:
ProgressView() ProgressView()
.scaleEffect(0.5) .scaleEffect(0.5)
} }

View file

@ -5,7 +5,8 @@ struct MainToolbarModifier: ViewModifier {
@Binding var category: XcodeListCategory @Binding var category: XcodeListCategory
@Binding var isInstalledOnly: Bool @Binding var isInstalledOnly: Bool
@Binding var isShowingInfoPane: Bool @Binding var isShowingInfoPane: Bool
@Binding var architectures: XcodeListArchitecture
func body(content: Content) -> some View { func body(content: Content) -> some View {
content content
.toolbar { toolbar } .toolbar { toolbar }
@ -13,63 +14,52 @@ struct MainToolbarModifier: ViewModifier {
private var toolbar: some ToolbarContent { private var toolbar: some ToolbarContent {
ToolbarItemGroup { ToolbarItemGroup {
ProgressButton( ProgressButton(
isInProgress: appState.isUpdating, isInProgress: appState.isUpdating,
action: appState.update action: appState.update
) { ) {
Label("Refresh", systemImage: "arrow.clockwise") Label("Refresh", systemImage: "arrow.clockwise")
} }
.keyboardShortcut(KeyEquivalent("r")) .keyboardShortcut(KeyEquivalent("r"))
.help("RefreshDescription") .help("RefreshDescription")
Spacer() Spacer()
Button(action: {
switch category { let isFiltering = isInstalledOnly || category != .all || architectures != .universal
case .all: category = .release Menu("Filter", systemImage: "line.horizontal.3.decrease.circle") {
case .release: category = .beta Section {
case .beta: category = .all Toggle("Installed Only", systemImage: "arrow.down.app", isOn: $isInstalledOnly) .labelStyle(.titleAndIcon)
} }
}) { .help("FilterInstalledDescription")
switch category {
case .all: Section {
Label("All", systemImage: "line.horizontal.3.decrease.circle") Picker("Category", selection: $category) {
case .release: Label("All", systemImage: "line.horizontal.3.decrease.circle")
if #available(macOS 11.3, *) { .tag(XcodeListCategory.all)
Label("ReleaseOnly", systemImage: "line.horizontal.3.decrease.circle.fill") Label("ReleaseOnly", systemImage: "line.horizontal.3.decrease.circle.fill")
.labelStyle(TitleAndIconLabelStyle()) .tag(XcodeListCategory.release)
.foregroundColor(.accentColor)
} else {
Label("ReleaseOnly", systemImage: "line.horizontal.3.decrease.circle.fill")
.labelStyle(TitleOnlyLabelStyle())
.foregroundColor(.accentColor)
}
case .beta:
if #available(macOS 11.3, *) {
Label("BetaOnly", systemImage: "line.horizontal.3.decrease.circle.fill") Label("BetaOnly", systemImage: "line.horizontal.3.decrease.circle.fill")
.labelStyle(TitleAndIconLabelStyle()) .tag(XcodeListCategory.beta)
.foregroundColor(.accentColor)
} else {
Label("BetaOnly", systemImage: "line.horizontal.3.decrease.circle.fill")
.labelStyle(TitleOnlyLabelStyle())
.foregroundColor(.accentColor)
} }
} }
} .help("FilterAvailableDescription")
.help("FilterAvailableDescription") .disabled(category.isManaged)
Button(action: { Section {
isInstalledOnly.toggle() Picker("Architecture", selection: $architectures) {
}) { Label("Universal", systemImage: "cpu.fill")
if isInstalledOnly { .tag(XcodeListArchitecture.universal)
Label("Filter", systemImage: "arrow.down.app.fill") Label("Apple Silicon", systemImage: "m4.button.horizontal")
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
} else { .tag(XcodeListArchitecture.appleSilicon)
Label("Filter", systemImage: "arrow.down.app") }
.help("FilterArchitecturesDescription")
.disabled(architectures.isManaged)
} }
.labelStyle(.titleAndIcon)
} }
.help("FilterInstalledDescription") .pickerStyle(.inline)
.symbolVariant(isFiltering ? .fill : .none)
} }
} }
} }
@ -78,13 +68,15 @@ extension View {
func mainToolbar( func mainToolbar(
category: Binding<XcodeListCategory>, category: Binding<XcodeListCategory>,
isInstalledOnly: Binding<Bool>, isInstalledOnly: Binding<Bool>,
isShowingInfoPane: Binding<Bool> isShowingInfoPane: Binding<Bool>,
architecture: Binding<XcodeListArchitecture>
) -> some View { ) -> some View {
self.modifier( modifier(
MainToolbarModifier( MainToolbarModifier(
category: category, category: category,
isInstalledOnly: isInstalledOnly, isInstalledOnly: isInstalledOnly,
isShowingInfoPane: isShowingInfoPane isShowingInfoPane: isShowingInfoPane,
architectures: architecture
) )
) )
} }

View file

@ -1,4 +1,5 @@
import Foundation import Foundation
import XcodesKit
enum XcodeListCategory: String, CaseIterable, Identifiable, CustomStringConvertible { enum XcodeListCategory: String, CaseIterable, Identifiable, CustomStringConvertible {
case all case all
@ -14,4 +15,22 @@ enum XcodeListCategory: String, CaseIterable, Identifiable, CustomStringConverti
case .beta: return localizeString("Beta") case .beta: return localizeString("Beta")
} }
} }
var isManaged: Bool { PreferenceKey.xcodeListCategory.isManaged() }
}
enum XcodeListArchitecture: String, CaseIterable, Identifiable, CustomStringConvertible {
case universal
case appleSilicon
var id: Self { self }
var description: String {
switch self {
case .universal: return localizeString("Universal")
case .appleSilicon: return localizeString("Apple Silicon")
}
}
var isManaged: Bool { PreferenceKey.xcodeListCategory.isManaged() }
} }

View file

@ -7,13 +7,16 @@ struct XcodeListView: View {
@Binding var selectedXcodeID: Xcode.ID? @Binding var selectedXcodeID: Xcode.ID?
private let searchText: String private let searchText: String
private let category: XcodeListCategory private let category: XcodeListCategory
private let architecture: XcodeListArchitecture
private let isInstalledOnly: Bool private let isInstalledOnly: Bool
@AppStorage(PreferenceKey.allowedMajorVersions.rawValue) private var allowedMajorVersions = Int.max
init(selectedXcodeID: Binding<Xcode.ID?>, searchText: String, category: XcodeListCategory, isInstalledOnly: Bool) {
init(selectedXcodeID: Binding<Xcode.ID?>, searchText: String, category: XcodeListCategory, isInstalledOnly: Bool, architecture: XcodeListArchitecture) {
self._selectedXcodeID = selectedXcodeID self._selectedXcodeID = selectedXcodeID
self.searchText = searchText self.searchText = searchText
self.category = category self.category = category
self.isInstalledOnly = isInstalledOnly self.isInstalledOnly = isInstalledOnly
self.architecture = architecture
} }
var visibleXcodes: [Xcode] { var visibleXcodes: [Xcode] {
@ -27,6 +30,27 @@ struct XcodeListView: View {
xcodes = appState.allXcodes.filter { $0.version.isPrerelease } xcodes = appState.allXcodes.filter { $0.version.isPrerelease }
} }
if architecture == .appleSilicon {
xcodes = xcodes.filter { $0.architectures == [.arm64] }
}
let latestMajor = xcodes.sorted(\.version)
.filter { $0.version.isNotPrerelease }
.last?
.version
.major
xcodes = xcodes.filter {
if $0.installState.notInstalled,
let latestMajor = latestMajor,
$0.version.major < (latestMajor - min(latestMajor,allowedMajorVersions)) {
return false
}
return true
}
if !searchText.isEmpty { if !searchText.isEmpty {
xcodes = xcodes.filter { $0.description.contains(searchText) } xcodes = xcodes.filter { $0.description.contains(searchText) }
} }
@ -45,7 +69,9 @@ struct XcodeListView: View {
.listStyle(.sidebar) .listStyle(.sidebar)
.safeAreaInset(edge: .bottom, spacing: 0) { .safeAreaInset(edge: .bottom, spacing: 0) {
PlatformsPocket() PlatformsPocket()
.padding() .padding(.horizontal)
.padding(.vertical, 8)
} }
} }
} }
@ -54,36 +80,51 @@ struct PlatformsPocket: View {
@SwiftUI.Environment(\.openWindow) private var openWindow @SwiftUI.Environment(\.openWindow) private var openWindow
var body: some View { var body: some View {
Button(action: { Button(action: {
openWindow(id: "platforms") } openWindow(id: "platforms")
}
) { ) {
VStack(spacing: 5) { if #available(macOS 26.0, *) {
Image(systemName: "square.3.layers.3d") platformsLabel
.font(.title) .glassEffect(in: .rect(cornerRadius: 8, style: .continuous))
Text("Platforms") } else {
.font(.callout) platformsLabel
.background(.quaternary.opacity(0.75))
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
} }
.frame(width: 70, height: 70)
.background(.quaternary)
.clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous))
} }
.buttonStyle(.plain) .buttonStyle(.plain)
} }
var platformsLabel: some View {
HStack(spacing: 5) {
Image(systemName: "square.3.layers.3d")
.font(.title3.weight(.medium))
Text("PlatformsDescription")
Spacer()
}
.font(.body.weight(.medium))
.padding(.horizontal)
.padding(.vertical, 12)
}
} }
struct XcodeListView_Previews: PreviewProvider { struct XcodeListView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
Group { Group {
XcodeListView(selectedXcodeID: .constant(nil), searchText: "", category: .all, isInstalledOnly: false) XcodeListView(selectedXcodeID: .constant(nil), searchText: "", category: .all, isInstalledOnly: false, architecture: .appleSilicon)
.environmentObject({ () -> AppState in .environmentObject({ () -> AppState in
let a = AppState() let a = AppState()
a.allXcodes = [ a.allXcodes = [
Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [Version("12.0.0+1234A")!, Version("12.0.0-RC+1234A")!], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil), Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [XcodeID(version: Version("12.0.0+1234A")!), XcodeID(version: Version("12.0.0-RC+1234A")!)], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil), Xcode(version: Version("12.3.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: true, icon: nil),
Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil), Xcode(version: Version("12.2.0")!, installState: .notInstalled, selected: false, icon: nil),
Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil), Xcode(version: Version("12.1.0")!, installState: .installing(.downloading(progress: configure(Progress(totalUnitCount: 100)) { $0.completedUnitCount = 40 })), selected: false, icon: nil),
Xcode(version: Version("12.0.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil), Xcode(version: Version("12.0.0")!, installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
Xcode(version: Version("10.1.0")!, installState: .notInstalled, selected: false, icon: nil),
Xcode(version: Version("10.0.0")!, installState: .installed(Path("/Applications/Xcode-10.0.0.app")!), selected: false, icon: nil),
Xcode(version: Version("9.0.0")!, installState: .notInstalled, selected: false, icon: nil),
] ]
return a return a
}()) }())

View file

@ -21,18 +21,23 @@ struct XcodeListViewRow: View {
.font(.subheadline) .font(.subheadline)
.foregroundColor(.secondary) .foregroundColor(.secondary)
.accessibility(label: Text("IdenticalBuilds")) .accessibility(label: Text("IdenticalBuilds"))
.accessibility(value: Text(xcode.identicalBuilds.map(\.appleDescription).joined(separator: ", "))) .accessibility(value: Text(xcode.identicalBuilds.map(\.version.appleDescription).joined(separator: ", ")))
.help("IdenticalBuilds.help") .help("IdenticalBuilds.help")
} }
if xcode.architectures?.isAppleSilicon ?? false {
Image(systemName: "m4.button.horizontal")
.font(.subheadline)
.foregroundColor(.secondary)
.accessibility(label: Text("Apple Silicon"))
.help("Apple Silicon")
}
} }
if case let .installed(path) = xcode.installState { if case let .installed(path) = xcode.installState {
Text(verbatim: path.string) Text(verbatim: path.string)
.font(.caption) .font(.caption)
.foregroundColor(.secondary) .foregroundColor(.secondary)
} else {
Text(verbatim: "")
.font(.caption)
} }
} }
@ -42,6 +47,7 @@ struct XcodeListViewRow: View {
.padding(.trailing, 16) .padding(.trailing, 16)
installControl(for: xcode) installControl(for: xcode)
} }
.padding(.vertical, 4)
.contextMenu { .contextMenu {
switch xcode.installState { switch xcode.installState {
case .notInstalled: case .notInstalled:
@ -75,9 +81,10 @@ struct XcodeListViewRow: View {
if let icon = xcode.icon { if let icon = xcode.icon {
Image(nsImage: icon) Image(nsImage: icon)
} else { } else {
Color.clear Image(xcode.version.isPrerelease ? "xcode-beta" : "xcode")
.resizable()
.frame(width: 32, height: 32) .frame(width: 32, height: 32)
.foregroundColor(.secondary) .opacity(0.2)
} }
} }
@ -157,7 +164,7 @@ struct XcodeListViewRow_Previews: PreviewProvider {
) )
XcodeListViewRow( XcodeListViewRow(
xcode: Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [Version("12.0.0-RC+1234A")!], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil), xcode: Xcode(version: Version("12.0.0+1234A")!, identicalBuilds: [XcodeID(version: Version("12.0.0-RC+1234A")!)], installState: .installed(Path("/Applications/Xcode-12.3.0.app")!), selected: false, icon: nil),
selected: false, selected: false,
appState: AppState() appState: AppState()
) )

View file

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Image.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "xcode.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -40,6 +40,6 @@
<key>SUFeedURL</key> <key>SUFeedURL</key>
<string>https://www.xcodes.app/appcast.xml</string> <string>https://www.xcodes.app/appcast.xml</string>
<key>SUPublicEDKey</key> <key>SUPublicEDKey</key>
<string>GrqOrFQHxfqoLFCAGc9luvsAWQifHtG9gQ3NVJ583tE=</string> <string>SEcz0vgUSeBTOoAXYe+64zea95G6lIf5NgzFs3InYJQ=</string>
</dict> </dict>
</plist> </plist>

View file

@ -1,4 +1,4 @@
{\rtf1\ansi\ansicpg1252\cocoartf2758 {\rtf1\ansi\ansicpg1252\cocoartf2865
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 .SFNS-Regular;} \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 .SFNS-Regular;}
{\colortbl;\red255\green255\blue255;} {\colortbl;\red255\green255\blue255;}
{\*\expandedcolortbl;;} {\*\expandedcolortbl;;}
@ -58,6 +58,33 @@ SOFTWARE.\
\ \
\ \
\fs34 LibFido2Swift\
\
\fs26 MIT License\
\
Copyright (c) 2024 Kino Roy\
\
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.\
\
\
\fs34 ErrorHandling\ \fs34 ErrorHandling\
\ \
@ -86,6 +113,241 @@ SOFTWARE.\
\ \
\ \
\fs34 big-num\
\
\fs26 MIT License\
\
Copyright (c) 2019 Adam Fowler\
\
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.\
\
\
\fs34 swift-crypto\
\
\fs26 \
Apache License\
Version 2.0, January 2004\
http://www.apache.org/licenses/\
\
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\
\
1. Definitions.\
\
"License" shall mean the terms and conditions for use, reproduction,\
and distribution as defined by Sections 1 through 9 of this document.\
\
"Licensor" shall mean the copyright owner or entity authorized by\
the copyright owner that is granting the License.\
\
"Legal Entity" shall mean the union of the acting entity and all\
other entities that control, are controlled by, or are under common\
control with that entity. For the purposes of this definition,\
"control" means (i) the power, direct or indirect, to cause the\
direction or management of such entity, whether by contract or\
otherwise, or (ii) ownership of fifty percent (50%) or more of the\
outstanding shares, or (iii) beneficial ownership of such entity.\
\
"You" (or "Your") shall mean an individual or Legal Entity\
exercising permissions granted by this License.\
\
"Source" form shall mean the preferred form for making modifications,\
including but not limited to software source code, documentation\
source, and configuration files.\
\
"Object" form shall mean any form resulting from mechanical\
transformation or translation of a Source form, including but\
not limited to compiled object code, generated documentation,\
and conversions to other media types.\
\
"Work" shall mean the work of authorship, whether in Source or\
Object form, made available under the License, as indicated by a\
copyright notice that is included in or attached to the work\
(an example is provided in the Appendix below).\
\
"Derivative Works" shall mean any work, whether in Source or Object\
form, that is based on (or derived from) the Work and for which the\
editorial revisions, annotations, elaborations, or other modifications\
represent, as a whole, an original work of authorship. For the purposes\
of this License, Derivative Works shall not include works that remain\
separable from, or merely link (or bind by name) to the interfaces of,\
the Work and Derivative Works thereof.\
\
"Contribution" shall mean any work of authorship, including\
the original version of the Work and any modifications or additions\
to that Work or Derivative Works thereof, that is intentionally\
submitted to Licensor for inclusion in the Work by the copyright owner\
or by an individual or Legal Entity authorized to submit on behalf of\
the copyright owner. For the purposes of this definition, "submitted"\
means any form of electronic, verbal, or written communication sent\
to the Licensor or its representatives, including but not limited to\
communication on electronic mailing lists, source code control systems,\
and issue tracking systems that are managed by, or on behalf of, the\
Licensor for the purpose of discussing and improving the Work, but\
excluding communication that is conspicuously marked or otherwise\
designated in writing by the copyright owner as "Not a Contribution."\
\
"Contributor" shall mean Licensor and any individual or Legal Entity\
on behalf of whom a Contribution has been received by Licensor and\
subsequently incorporated within the Work.\
\
2. Grant of Copyright License. Subject to the terms and conditions of\
this License, each Contributor hereby grants to You a perpetual,\
worldwide, non-exclusive, no-charge, royalty-free, irrevocable\
copyright license to reproduce, prepare Derivative Works of,\
publicly display, publicly perform, sublicense, and distribute the\
Work and such Derivative Works in Source or Object form.\
\
3. Grant of Patent License. Subject to the terms and conditions of\
this License, each Contributor hereby grants to You a perpetual,\
worldwide, non-exclusive, no-charge, royalty-free, irrevocable\
(except as stated in this section) patent license to make, have made,\
use, offer to sell, sell, import, and otherwise transfer the Work,\
where such license applies only to those patent claims licensable\
by such Contributor that are necessarily infringed by their\
Contribution(s) alone or by combination of their Contribution(s)\
with the Work to which such Contribution(s) was submitted. If You\
institute patent litigation against any entity (including a\
cross-claim or counterclaim in a lawsuit) alleging that the Work\
or a Contribution incorporated within the Work constitutes direct\
or contributory patent infringement, then any patent licenses\
granted to You under this License for that Work shall terminate\
as of the date such litigation is filed.\
\
4. Redistribution. You may reproduce and distribute copies of the\
Work or Derivative Works thereof in any medium, with or without\
modifications, and in Source or Object form, provided that You\
meet the following conditions:\
\
(a) You must give any other recipients of the Work or\
Derivative Works a copy of this License; and\
\
(b) You must cause any modified files to carry prominent notices\
stating that You changed the files; and\
\
(c) You must retain, in the Source form of any Derivative Works\
that You distribute, all copyright, patent, trademark, and\
attribution notices from the Source form of the Work,\
excluding those notices that do not pertain to any part of\
the Derivative Works; and\
\
(d) If the Work includes a "NOTICE" text file as part of its\
distribution, then any Derivative Works that You distribute must\
include a readable copy of the attribution notices contained\
within such NOTICE file, excluding those notices that do not\
pertain to any part of the Derivative Works, in at least one\
of the following places: within a NOTICE text file distributed\
as part of the Derivative Works; within the Source form or\
documentation, if provided along with the Derivative Works; or,\
within a display generated by the Derivative Works, if and\
wherever such third-party notices normally appear. The contents\
of the NOTICE file are for informational purposes only and\
do not modify the License. You may add Your own attribution\
notices within Derivative Works that You distribute, alongside\
or as an addendum to the NOTICE text from the Work, provided\
that such additional attribution notices cannot be construed\
as modifying the License.\
\
You may add Your own copyright statement to Your modifications and\
may provide additional or different license terms and conditions\
for use, reproduction, or distribution of Your modifications, or\
for any such Derivative Works as a whole, provided Your use,\
reproduction, and distribution of the Work otherwise complies with\
the conditions stated in this License.\
\
5. Submission of Contributions. Unless You explicitly state otherwise,\
any Contribution intentionally submitted for inclusion in the Work\
by You to the Licensor shall be under the terms and conditions of\
this License, without any additional terms or conditions.\
Notwithstanding the above, nothing herein shall supersede or modify\
the terms of any separate license agreement you may have executed\
with Licensor regarding such Contributions.\
\
6. Trademarks. This License does not grant permission to use the trade\
names, trademarks, service marks, or product names of the Licensor,\
except as required for reasonable and customary use in describing the\
origin of the Work and reproducing the content of the NOTICE file.\
\
7. Disclaimer of Warranty. Unless required by applicable law or\
agreed to in writing, Licensor provides the Work (and each\
Contributor provides its Contributions) on an "AS IS" BASIS,\
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\
implied, including, without limitation, any warranties or conditions\
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\
PARTICULAR PURPOSE. You are solely responsible for determining the\
appropriateness of using or redistributing the Work and assume any\
risks associated with Your exercise of permissions under this License.\
\
8. Limitation of Liability. In no event and under no legal theory,\
whether in tort (including negligence), contract, or otherwise,\
unless required by applicable law (such as deliberate and grossly\
negligent acts) or agreed to in writing, shall any Contributor be\
liable to You for damages, including any direct, indirect, special,\
incidental, or consequential damages of any character arising as a\
result of this License or out of the use or inability to use the\
Work (including but not limited to damages for loss of goodwill,\
work stoppage, computer failure or malfunction, or any and all\
other commercial damages or losses), even if such Contributor\
has been advised of the possibility of such damages.\
\
9. Accepting Warranty or Additional Liability. While redistributing\
the Work or Derivative Works thereof, You may choose to offer,\
and charge a fee for, acceptance of support, warranty, indemnity,\
or other liability obligations and/or rights consistent with this\
License. However, in accepting such obligations, You may act only\
on Your own behalf and on Your sole responsibility, not on behalf\
of any other Contributor, and only if You agree to indemnify,\
defend, and hold each Contributor harmless for any liability\
incurred by, or claims asserted against, such Contributor by reason\
of your accepting any such warranty or additional liability.\
\
END OF TERMS AND CONDITIONS\
\
APPENDIX: How to apply the Apache License to your work.\
\
To apply the Apache License to your work, attach the following\
boilerplate notice, with the fields enclosed by brackets "[]"\
replaced with your own identifying information. (Don't include\
the brackets!) The text should be enclosed in the appropriate\
comment syntax for the file format. We also recommend that a\
file or class name and description of purpose be included on the\
same "printed page" as the copyright notice for easier\
identification within third-party archives.\
\
Copyright [yyyy] [name of copyright owner]\
\
Licensed under the Apache License, Version 2.0 (the "License");\
you may not use this file except in compliance with the License.\
You may obtain a copy of the License at\
\
http://www.apache.org/licenses/LICENSE-2.0\
\
Unless required by applicable law or agreed to in writing, software\
distributed under the License is distributed on an "AS IS" BASIS,\
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\
See the License for the specific language governing permissions and\
limitations under the License.\
\
\
\fs34 Path.swift\ \fs34 Path.swift\
\ \
@ -552,12 +814,219 @@ For more information, please refer to &lt;<http://unlicense.org/>&gt;\
\ \
\ \
\fs34 swift-srp\
\
\fs26 Apache License\
Version 2.0, January 2004\
http://www.apache.org/licenses/\
\
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\
\
1. Definitions.\
\
"License" shall mean the terms and conditions for use, reproduction,\
and distribution as defined by Sections 1 through 9 of this document.\
\
"Licensor" shall mean the copyright owner or entity authorized by\
the copyright owner that is granting the License.\
\
"Legal Entity" shall mean the union of the acting entity and all\
other entities that control, are controlled by, or are under common\
control with that entity. For the purposes of this definition,\
"control" means (i) the power, direct or indirect, to cause the\
direction or management of such entity, whether by contract or\
otherwise, or (ii) ownership of fifty percent (50%) or more of the\
outstanding shares, or (iii) beneficial ownership of such entity.\
\
"You" (or "Your") shall mean an individual or Legal Entity\
exercising permissions granted by this License.\
\
"Source" form shall mean the preferred form for making modifications,\
including but not limited to software source code, documentation\
source, and configuration files.\
\
"Object" form shall mean any form resulting from mechanical\
transformation or translation of a Source form, including but\
not limited to compiled object code, generated documentation,\
and conversions to other media types.\
\
"Work" shall mean the work of authorship, whether in Source or\
Object form, made available under the License, as indicated by a\
copyright notice that is included in or attached to the work\
(an example is provided in the Appendix below).\
\
"Derivative Works" shall mean any work, whether in Source or Object\
form, that is based on (or derived from) the Work and for which the\
editorial revisions, annotations, elaborations, or other modifications\
represent, as a whole, an original work of authorship. For the purposes\
of this License, Derivative Works shall not include works that remain\
separable from, or merely link (or bind by name) to the interfaces of,\
the Work and Derivative Works thereof.\
\
"Contribution" shall mean any work of authorship, including\
the original version of the Work and any modifications or additions\
to that Work or Derivative Works thereof, that is intentionally\
submitted to Licensor for inclusion in the Work by the copyright owner\
or by an individual or Legal Entity authorized to submit on behalf of\
the copyright owner. For the purposes of this definition, "submitted"\
means any form of electronic, verbal, or written communication sent\
to the Licensor or its representatives, including but not limited to\
communication on electronic mailing lists, source code control systems,\
and issue tracking systems that are managed by, or on behalf of, the\
Licensor for the purpose of discussing and improving the Work, but\
excluding communication that is conspicuously marked or otherwise\
designated in writing by the copyright owner as "Not a Contribution."\
\
"Contributor" shall mean Licensor and any individual or Legal Entity\
on behalf of whom a Contribution has been received by Licensor and\
subsequently incorporated within the Work.\
\
2. Grant of Copyright License. Subject to the terms and conditions of\
this License, each Contributor hereby grants to You a perpetual,\
worldwide, non-exclusive, no-charge, royalty-free, irrevocable\
copyright license to reproduce, prepare Derivative Works of,\
publicly display, publicly perform, sublicense, and distribute the\
Work and such Derivative Works in Source or Object form.\
\
3. Grant of Patent License. Subject to the terms and conditions of\
this License, each Contributor hereby grants to You a perpetual,\
worldwide, non-exclusive, no-charge, royalty-free, irrevocable\
(except as stated in this section) patent license to make, have made,\
use, offer to sell, sell, import, and otherwise transfer the Work,\
where such license applies only to those patent claims licensable\
by such Contributor that are necessarily infringed by their\
Contribution(s) alone or by combination of their Contribution(s)\
with the Work to which such Contribution(s) was submitted. If You\
institute patent litigation against any entity (including a\
cross-claim or counterclaim in a lawsuit) alleging that the Work\
or a Contribution incorporated within the Work constitutes direct\
or contributory patent infringement, then any patent licenses\
granted to You under this License for that Work shall terminate\
as of the date such litigation is filed.\
\
4. Redistribution. You may reproduce and distribute copies of the\
Work or Derivative Works thereof in any medium, with or without\
modifications, and in Source or Object form, provided that You\
meet the following conditions:\
\
(a) You must give any other recipients of the Work or\
Derivative Works a copy of this License; and\
\
(b) You must cause any modified files to carry prominent notices\
stating that You changed the files; and\
\
(c) You must retain, in the Source form of any Derivative Works\
that You distribute, all copyright, patent, trademark, and\
attribution notices from the Source form of the Work,\
excluding those notices that do not pertain to any part of\
the Derivative Works; and\
\
(d) If the Work includes a "NOTICE" text file as part of its\
distribution, then any Derivative Works that You distribute must\
include a readable copy of the attribution notices contained\
within such NOTICE file, excluding those notices that do not\
pertain to any part of the Derivative Works, in at least one\
of the following places: within a NOTICE text file distributed\
as part of the Derivative Works; within the Source form or\
documentation, if provided along with the Derivative Works; or,\
within a display generated by the Derivative Works, if and\
wherever such third-party notices normally appear. The contents\
of the NOTICE file are for informational purposes only and\
do not modify the License. You may add Your own attribution\
notices within Derivative Works that You distribute, alongside\
or as an addendum to the NOTICE text from the Work, provided\
that such additional attribution notices cannot be construed\
as modifying the License.\
\
You may add Your own copyright statement to Your modifications and\
may provide additional or different license terms and conditions\
for use, reproduction, or distribution of Your modifications, or\
for any such Derivative Works as a whole, provided Your use,\
reproduction, and distribution of the Work otherwise complies with\
the conditions stated in this License.\
\
5. Submission of Contributions. Unless You explicitly state otherwise,\
any Contribution intentionally submitted for inclusion in the Work\
by You to the Licensor shall be under the terms and conditions of\
this License, without any additional terms or conditions.\
Notwithstanding the above, nothing herein shall supersede or modify\
the terms of any separate license agreement you may have executed\
with Licensor regarding such Contributions.\
\
6. Trademarks. This License does not grant permission to use the trade\
names, trademarks, service marks, or product names of the Licensor,\
except as required for reasonable and customary use in describing the\
origin of the Work and reproducing the content of the NOTICE file.\
\
7. Disclaimer of Warranty. Unless required by applicable law or\
agreed to in writing, Licensor provides the Work (and each\
Contributor provides its Contributions) on an "AS IS" BASIS,\
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\
implied, including, without limitation, any warranties or conditions\
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\
PARTICULAR PURPOSE. You are solely responsible for determining the\
appropriateness of using or redistributing the Work and assume any\
risks associated with Your exercise of permissions under this License.\
\
8. Limitation of Liability. In no event and under no legal theory,\
whether in tort (including negligence), contract, or otherwise,\
unless required by applicable law (such as deliberate and grossly\
negligent acts) or agreed to in writing, shall any Contributor be\
liable to You for damages, including any direct, indirect, special,\
incidental, or consequential damages of any character arising as a\
result of this License or out of the use or inability to use the\
Work (including but not limited to damages for loss of goodwill,\
work stoppage, computer failure or malfunction, or any and all\
other commercial damages or losses), even if such Contributor\
has been advised of the possibility of such damages.\
\
9. Accepting Warranty or Additional Liability. While redistributing\
the Work or Derivative Works thereof, You may choose to offer,\
and charge a fee for, acceptance of support, warranty, indemnity,\
or other liability obligations and/or rights consistent with this\
License. However, in accepting such obligations, You may act only\
on Your own behalf and on Your sole responsibility, not on behalf\
of any other Contributor, and only if You agree to indemnify,\
defend, and hold each Contributor harmless for any liability\
incurred by, or claims asserted against, such Contributor by reason\
of your accepting any such warranty or additional liability.\
\
END OF TERMS AND CONDITIONS\
\
APPENDIX: How to apply the Apache License to your work.\
\
To apply the Apache License to your work, attach the following\
boilerplate notice, with the fields enclosed by brackets "[]"\
replaced with your own identifying information. (Don't include\
the brackets!) The text should be enclosed in the appropriate\
comment syntax for the file format. We also recommend that a\
file or class name and description of purpose be included on the\
same "printed page" as the copyright notice for easier\
identification within third-party archives.\
\
Copyright [yyyy] [name of copyright owner]\
\
Licensed under the Apache License, Version 2.0 (the "License");\
you may not use this file except in compliance with the License.\
You may obtain a copy of the License at\
\
http://www.apache.org/licenses/LICENSE-2.0\
\
Unless required by applicable law or agreed to in writing, software\
distributed under the License is distributed on an "AS IS" BASIS,\
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\
See the License for the specific language governing permissions and\
limitations under the License.\
\
\
\fs34 DockProgress\ \fs34 DockProgress\
\ \
\fs26 MIT License\ \fs26 MIT License\
\ \
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)\ Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)\
\ \
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:\ 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:\
\ \
@ -595,176 +1064,6 @@ SOFTWARE.\
\ \
\ \
\fs34 unxip\
\
\fs26 GNU LESSER GENERAL PUBLIC LICENSE\
Version 3, 29 June 2007\
\
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\
Everyone is permitted to copy and distribute verbatim copies\
of this license document, but changing it is not allowed.\
\
\
This version of the GNU Lesser General Public License incorporates\
the terms and conditions of version 3 of the GNU General Public\
License, supplemented by the additional permissions listed below.\
\
0. Additional Definitions.\
\
As used herein, "this License" refers to version 3 of the GNU Lesser\
General Public License, and the "GNU GPL" refers to version 3 of the GNU\
General Public License.\
\
"The Library" refers to a covered work governed by this License,\
other than an Application or a Combined Work as defined below.\
\
An "Application" is any work that makes use of an interface provided\
by the Library, but which is not otherwise based on the Library.\
Defining a subclass of a class defined by the Library is deemed a mode\
of using an interface provided by the Library.\
\
A "Combined Work" is a work produced by combining or linking an\
Application with the Library. The particular version of the Library\
with which the Combined Work was made is also called the "Linked\
Version".\
\
The "Minimal Corresponding Source" for a Combined Work means the\
Corresponding Source for the Combined Work, excluding any source code\
for portions of the Combined Work that, considered in isolation, are\
based on the Application, and not on the Linked Version.\
\
The "Corresponding Application Code" for a Combined Work means the\
object code and/or source code for the Application, including any data\
and utility programs needed for reproducing the Combined Work from the\
Application, but excluding the System Libraries of the Combined Work.\
\
1. Exception to Section 3 of the GNU GPL.\
\
You may convey a covered work under sections 3 and 4 of this License\
without being bound by section 3 of the GNU GPL.\
\
2. Conveying Modified Versions.\
\
If you modify a copy of the Library, and, in your modifications, a\
facility refers to a function or data to be supplied by an Application\
that uses the facility (other than as an argument passed when the\
facility is invoked), then you may convey a copy of the modified\
version:\
\
a) under this License, provided that you make a good faith effort to\
ensure that, in the event an Application does not supply the\
function or data, the facility still operates, and performs\
whatever part of its purpose remains meaningful, or\
\
b) under the GNU GPL, with none of the additional permissions of\
this License applicable to that copy.\
\
3. Object Code Incorporating Material from Library Header Files.\
\
The object code form of an Application may incorporate material from\
a header file that is part of the Library. You may convey such object\
code under terms of your choice, provided that, if the incorporated\
material is not limited to numerical parameters, data structure\
layouts and accessors, or small macros, inline functions and templates\
(ten or fewer lines in length), you do both of the following:\
\
a) Give prominent notice with each copy of the object code that the\
Library is used in it and that the Library and its use are\
covered by this License.\
\
b) Accompany the object code with a copy of the GNU GPL and this license\
document.\
\
4. Combined Works.\
\
You may convey a Combined Work under terms of your choice that,\
taken together, effectively do not restrict modification of the\
portions of the Library contained in the Combined Work and reverse\
engineering for debugging such modifications, if you also do each of\
the following:\
\
a) Give prominent notice with each copy of the Combined Work that\
the Library is used in it and that the Library and its use are\
covered by this License.\
\
b) Accompany the Combined Work with a copy of the GNU GPL and this license\
document.\
\
c) For a Combined Work that displays copyright notices during\
execution, include the copyright notice for the Library among\
these notices, as well as a reference directing the user to the\
copies of the GNU GPL and this license document.\
\
d) Do one of the following:\
\
0) Convey the Minimal Corresponding Source under the terms of this\
License, and the Corresponding Application Code in a form\
suitable for, and under terms that permit, the user to\
recombine or relink the Application with a modified version of\
the Linked Version to produce a modified Combined Work, in the\
manner specified by section 6 of the GNU GPL for conveying\
Corresponding Source.\
\
1) Use a suitable shared library mechanism for linking with the\
Library. A suitable mechanism is one that (a) uses at run time\
a copy of the Library already present on the user's computer\
system, and (b) will operate properly with a modified version\
of the Library that is interface-compatible with the Linked\
Version.\
\
e) Provide Installation Information, but only if you would otherwise\
be required to provide such information under section 6 of the\
GNU GPL, and only to the extent that such information is\
necessary to install and execute a modified version of the\
Combined Work produced by recombining or relinking the\
Application with a modified version of the Linked Version. (If\
you use option 4d0, the Installation Information must accompany\
the Minimal Corresponding Source and Corresponding Application\
Code. If you use option 4d1, you must provide the Installation\
Information in the manner specified by section 6 of the GNU GPL\
for conveying Corresponding Source.)\
\
5. Combined Libraries.\
\
You may place library facilities that are a work based on the\
Library side by side in a single library together with other library\
facilities that are not Applications and are not covered by this\
License, and convey such a combined library under terms of your\
choice, if you do both of the following:\
\
a) Accompany the combined library with a copy of the same work based\
on the Library, uncombined with any other library facilities,\
conveyed under the terms of this License.\
\
b) Give prominent notice with the combined library that part of it\
is a work based on the Library, and explaining where to find the\
accompanying uncombined form of the same work.\
\
6. Revised Versions of the GNU Lesser General Public License.\
\
The Free Software Foundation may publish revised and/or new versions\
of the GNU Lesser General Public License from time to time. Such new\
versions will be similar in spirit to the present version, but may\
differ in detail to address new problems or concerns.\
\
Each version is given a distinguishing version number. If the\
Library as you received it specifies that a certain numbered version\
of the GNU Lesser General Public License "or any later version"\
applies to it, you have the option of following the terms and\
conditions either of that published version or of any later version\
published by the Free Software Foundation. If the Library as you\
received it does not specify a version number of the GNU Lesser\
General Public License, you may choose any version of the GNU Lesser\
General Public License ever published by the Free Software Foundation.\
\
If the Library as you received it specifies that a proxy can decide\
whether future versions of the GNU Lesser General Public License shall\
apply, that proxy's public statement of acceptance of any version is\
permanent authorization for you to choose that version for the\
Library.\
\
\fs34 data\ \fs34 data\
\ \

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 KiB

View file

@ -0,0 +1,50 @@
{
"fill" : {
"automatic-gradient" : "extended-srgb:0.00000,0.47843,1.00000,1.00000"
},
"groups" : [
{
"layers" : [
{
"glass" : false,
"hidden" : false,
"image-name-specializations" : [
{
"value" : "Light.png"
},
{
"appearance" : "dark",
"value" : "Dark.png"
},
{
"appearance" : "tinted",
"value" : "Mono.png"
}
],
"name" : "Dark",
"position" : {
"scale" : 1,
"translation-in-points" : [
0,
0
]
}
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"specular" : false,
"translucency" : {
"enabled" : false,
"value" : 0.5
}
}
],
"supported-platforms" : {
"squares" : [
"macOS"
]
}
}

View file

@ -11,7 +11,7 @@ struct XcodesApp: App {
@StateObject private var updater = ObservableUpdater() @StateObject private var updater = ObservableUpdater()
var body: some Scene { var body: some Scene {
WindowGroup("Xcodes") { Window("Xcodes", id: "main") {
MainWindow() MainWindow()
.environmentObject(appState) .environmentObject(appState)
.environmentObject(updater) .environmentObject(updater)
@ -51,19 +51,19 @@ struct XcodesApp: App {
CommandGroup(replacing: CommandGroupPlacement.help) { CommandGroup(replacing: CommandGroupPlacement.help) {
Button("Menu.GitHubRepo") { Button("Menu.GitHubRepo") {
let xcodesRepoURL = URL(string: "https://github.com/RobotsAndPencils/XcodesApp/")! let xcodesRepoURL = URL(string: "https://github.com/XcodesOrg/XcodesApp/")!
openURL(xcodesRepoURL) openURL(xcodesRepoURL)
} }
Divider() Divider()
Button("Menu.ReportABug") { Button("Menu.ReportABug") {
let bugReportURL = URL(string: "https://github.com/RobotsAndPencils/XcodesApp/issues/new?assignees=&labels=bug&template=bug_report.md&title=")! let bugReportURL = URL(string: "https://github.com/XcodesOrg/XcodesApp/issues/new?assignees=&labels=bug&template=bug_report.md&title=")!
openURL(bugReportURL) openURL(bugReportURL)
} }
Button("Menu.RequestNewFeature") { Button("Menu.RequestNewFeature") {
let featureRequestURL = URL(string: "https://github.com/RobotsAndPencils/XcodesApp/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=")! let featureRequestURL = URL(string: "https://github.com/XcodesOrg/XcodesApp/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=")!
openURL(featureRequestURL) openURL(featureRequestURL)
} }
} }
@ -166,12 +166,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
} }
func applicationDidFinishLaunching(_: Notification) {} func applicationDidFinishLaunching(_: Notification) {}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return Current.defaults.bool(forKey: "terminateAfterLastWindowClosed") ?? false
}
} }
func localizeString(_ key: String, comment: String = "") -> String { func localizeString(_ key: String, comment: String = "") -> String {
if #available(macOS 12, *) { return String(localized: String.LocalizationValue(key))
return String(localized: String.LocalizationValue(key))
} else {
return NSLocalizedString(key, comment: comment)
}
} }

View file

@ -5,7 +5,7 @@ import PackageDescription
let package = Package( let package = Package(
name: "XcodesKit", name: "XcodesKit",
platforms: [.macOS(.v11)], platforms: [.macOS(.v13)],
products: [ products: [
// Products define the executables and libraries a package produces, and make them visible to other packages. // Products define the executables and libraries a package produces, and make them visible to other packages.
.library( .library(

View file

@ -37,8 +37,10 @@ public struct CoreSimulatorImage: Decodable, Identifiable, Equatable {
public struct CoreSimulatorRuntimeInfo: Decodable { public struct CoreSimulatorRuntimeInfo: Decodable {
public let build: String public let build: String
public let supportedArchitectures: [Architecture]?
public init(build: String) { public init(build: String, supportedArchitectures: [Architecture]? = nil) {
self.build = build self.build = build
self.supportedArchitectures = supportedArchitectures
} }
} }

View file

@ -11,7 +11,8 @@ public struct DownloadableRuntimesResponse: Codable {
public struct DownloadableRuntime: Codable, Identifiable, Hashable { public struct DownloadableRuntime: Codable, Identifiable, Hashable {
public let category: Category public let category: Category
public let simulatorVersion: SimulatorVersion public let simulatorVersion: SimulatorVersion
public let source: String public let source: String?
public let architectures: [Architecture]?
public let dictionaryVersion: Int public let dictionaryVersion: Int
public let contentType: ContentType public let contentType: ContentType
public let platform: Platform public let platform: Platform
@ -21,16 +22,19 @@ public struct DownloadableRuntime: Codable, Identifiable, Hashable {
public let hostRequirements: HostRequirements? public let hostRequirements: HostRequirements?
public let name: String public let name: String
public let authentication: Authentication? public let authentication: Authentication?
public var url: URL { public var url: URL? {
return URL(string: source)! if let source {
return URL(string: source)!
}
return nil
} }
public var downloadPath: String { public var downloadPath: String? {
url.path url?.path
} }
// dynamically updated - not decoded // dynamically updated - not decoded
public var installState: RuntimeInstallState = .notInstalled public var installState: RuntimeInstallState = .notInstalled
public var sdkBuildUpdate: String? public var sdkBuildUpdate: [String]?
enum CodingKeys: CodingKey { enum CodingKeys: CodingKey {
case category case category
@ -46,10 +50,11 @@ public struct DownloadableRuntime: Codable, Identifiable, Hashable {
case name case name
case authentication case authentication
case sdkBuildUpdate case sdkBuildUpdate
case architectures
} }
var betaNumber: Int? { var betaNumber: Int? {
enum Regex { static let shared = try! NSRegularExpression(pattern: "b[0-9]+$") } enum Regex { static let shared = try! NSRegularExpression(pattern: "b[0-9]+") }
guard var foundString = Regex.shared.firstString(in: identifier) else { return nil } guard var foundString = Regex.shared.firstString(in: identifier) else { return nil }
foundString.removeFirst() foundString.removeFirst()
return Int(foundString)! return Int(foundString)!
@ -91,6 +96,7 @@ public struct SDKToSimulatorMapping: Codable {
public let sdkBuildUpdate: String public let sdkBuildUpdate: String
public let simulatorBuildUpdate: String public let simulatorBuildUpdate: String
public let sdkIdentifier: String public let sdkIdentifier: String
public let downloadableIdentifiers: [String]?
} }
extension DownloadableRuntime { extension DownloadableRuntime {
@ -117,6 +123,7 @@ extension DownloadableRuntime {
public enum ContentType: String, Codable { public enum ContentType: String, Codable {
case diskImage = "diskImage" case diskImage = "diskImage"
case package = "package" case package = "package"
case cryptexDiskImage = "cryptexDiskImage"
} }
public enum Platform: String, Codable { public enum Platform: String, Codable {
@ -163,6 +170,7 @@ public struct InstalledRuntime: Decodable {
let state: String let state: String
let version: String let version: String
let sizeBytes: Int? let sizeBytes: Int?
let supportedArchitectures: [Architecture]?
} }
extension InstalledRuntime { extension InstalledRuntime {

View file

@ -9,6 +9,7 @@ import Foundation
// A numbered step // A numbered step
public enum XcodeInstallationStep: Equatable, CustomStringConvertible { public enum XcodeInstallationStep: Equatable, CustomStringConvertible {
case authenticating
case downloading(progress: Progress) case downloading(progress: Progress)
case unarchiving case unarchiving
case moving(destination: String) case moving(destination: String)
@ -22,6 +23,8 @@ public enum XcodeInstallationStep: Equatable, CustomStringConvertible {
public var message: String { public var message: String {
switch self { switch self {
case .authenticating:
return localizeString("Authenticating")
case .downloading: case .downloading:
return localizeString("Downloading") return localizeString("Downloading")
case .unarchiving: case .unarchiving:
@ -39,16 +42,17 @@ public enum XcodeInstallationStep: Equatable, CustomStringConvertible {
public var stepNumber: Int { public var stepNumber: Int {
switch self { switch self {
case .downloading: return 1 case .authenticating: return 1
case .unarchiving: return 2 case .downloading: return 2
case .moving: return 3 case .unarchiving: return 3
case .trashingArchive: return 4 case .moving: return 4
case .checkingSecurity: return 5 case .trashingArchive: return 5
case .finishing: return 6 case .checkingSecurity: return 6
case .finishing: return 7
} }
} }
public var stepCount: Int { 6 } public var stepCount: Int { 7 }
} }
func localizeString(_ key: String, comment: String = "") -> String { func localizeString(_ key: String, comment: String = "") -> String {

View file

@ -0,0 +1,71 @@
//
// Architecture.swift
// XcodesKit
//
// Created by Matt Kiazyk on 2025-08-23.
//
import Foundation
/// The name of an Architecture.
public enum Architecture: String, Codable, Equatable, Hashable, Identifiable, CaseIterable {
public var id: Self { self }
/// The Arm64 architecture (Apple Silicon)
case arm64 = "arm64"
/// The X86\_64 architecture (64-bit Intel)
case x86_64 = "x86_64"
public var displayString: String {
switch self {
case .arm64:
return "Apple Silicon"
case .x86_64:
return "Intel"
}
}
public var iconName: String {
switch self {
case .arm64:
return "m4.button.horizontal"
case .x86_64:
return "cpu.fill"
}
}
}
public enum ArchitectureVariant: String, Codable, Equatable, Hashable, Identifiable, CaseIterable {
public var id: Self { self }
case universal
case appleSilicon
public var displayString: String {
switch self {
case .appleSilicon:
return "Apple Silicon"
case .universal:
return "Universal"
}
}
public var iconName: String {
switch self {
case .appleSilicon:
return "m4.button.horizontal"
case .universal:
return "cpu.fill"
}
}
}
extension Array where Element == Architecture {
public var isAppleSilicon: Bool {
self == [.arm64]
}
public var isUniversal: Bool {
self.contains([.arm64, .x86_64])
}
}

View file

@ -0,0 +1,20 @@
//
// Checksums.swift
// xcodereleases
//
// Created by Xcode Releases on 9/17/20.
// Copyright © 2020 Xcode Releases. All rights reserved.
//
import Foundation
public struct Checksums: Codable {
public let sha1: String?
public init(sha1: String? = nil) {
self.sha1 = sha1
}
}

View file

@ -0,0 +1,33 @@
//
// Compiler.swift
// xcodereleases
//
// Created by Xcode Releases on 4/4/18.
// Copyright © 2018 Xcode Releases. All rights reserved.
//
import Foundation
public struct Compilers: Codable {
public let gcc: Array<XcodeVersion>?
public let llvm_gcc: Array<XcodeVersion>?
public let llvm: Array<XcodeVersion>?
public let clang: Array<XcodeVersion>?
public let swift: Array<XcodeVersion>?
public init(gcc: XcodeVersion? = nil, llvm_gcc: XcodeVersion? = nil, llvm: XcodeVersion? = nil, clang: XcodeVersion? = nil, swift: XcodeVersion? = nil) {
self.gcc = gcc.map { [$0] }
self.llvm_gcc = llvm_gcc.map { [$0] }
self.llvm = llvm.map { [$0] }
self.clang = clang.map { [$0] }
self.swift = swift.map { [$0] }
}
public init(gcc: Array<XcodeVersion>?, llvm_gcc: Array<XcodeVersion>?, llvm: Array<XcodeVersion>?, clang: Array<XcodeVersion>?, swift: Array<XcodeVersion>?) {
self.gcc = gcc?.isEmpty == true ? nil : gcc
self.llvm_gcc = llvm_gcc?.isEmpty == true ? nil : llvm_gcc
self.llvm = llvm?.isEmpty == true ? nil : llvm
self.clang = clang?.isEmpty == true ? nil : clang
self.swift = swift?.isEmpty == true ? nil : swift
}
}

View file

@ -0,0 +1,32 @@
//
// Link.swift
// xcodereleases
//
// Created by Xcode Releases on 4/5/18.
// Copyright © 2018 Xcode Releases. All rights reserved.
//
import Foundation
public struct Link: Codable {
public let url: URL
public let sizeMB: Int?
/// The platforms supported by this link, if applicable.
public var architectures: [Architecture]?
// public init(_ string: String, _ size: Int? = nil, _ architectures: [Architecture]? = nil) {
// self.url = URL(string: string)!
// self.sizeMB = size
// self.architectures = architectures
// }
}
public struct Links: Codable {
public let download: Link?
public let notes: Link?
public init(download: Link? = nil, notes: Link? = nil) {
self.download = download
self.notes = notes
}
}

View file

@ -0,0 +1,59 @@
//
// Release.swift
// xcodereleases
//
// Created by Xcode Releases on 4/4/18.
// Copyright © 2018 Xcode Releases. All rights reserved.
//
import Foundation
public enum Release: Codable {
public enum CodingKeys: String, CodingKey {
case gm, gmSeed, rc, beta, dp, release
}
public var isGM: Bool {
guard case .gm = self else { return false }
return true
}
case gm
case gmSeed(Int)
case rc(Int)
case beta(Int)
case dp(Int)
case release
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let _ = try container.decodeIfPresent(Bool.self, forKey: .gm) {
self = .gm
} else if let v = try container.decodeIfPresent(Int.self, forKey: .gmSeed) {
self = .gmSeed(v)
} else if let v = try container.decodeIfPresent(Int.self, forKey: .rc) {
self = .rc(v)
} else if let v = try container.decodeIfPresent(Int.self, forKey: .beta) {
self = .beta(v)
} else if let v = try container.decodeIfPresent(Int.self, forKey: .dp) {
self = .dp(v)
} else if let _ = try container.decodeIfPresent(Bool.self, forKey: .release) {
self = .release
} else {
fatalError("Unreachable")
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .gm: try container.encode(true, forKey: .gm)
case .gmSeed(let v): try container.encode(v, forKey: .gmSeed)
case .rc(let v): try container.encode(v, forKey: .rc)
case .beta(let v): try container.encode(v, forKey: .beta)
case .dp(let v): try container.encode(v, forKey: .dp)
case .release: try container.encode(true, forKey: .release)
}
}
}

View file

@ -0,0 +1,57 @@
//
// SDKs.swift
// xcodereleases
//
// Created by Xcode Releases on 4/4/18.
// Copyright © 2018 Xcode Releases. All rights reserved.
//
import Foundation
public struct SDKs: Codable {
public let macOS: Array<XcodeVersion>?
public let iOS: Array<XcodeVersion>?
public let watchOS: Array<XcodeVersion>?
public let tvOS: Array<XcodeVersion>?
public let visionOS: Array<XcodeVersion>?
public init(macOS: XcodeVersion? = nil, iOS: XcodeVersion? = nil, watchOS: XcodeVersion? = nil, tvOS: XcodeVersion? = nil, visionOS: XcodeVersion? = nil) {
self.macOS = macOS.map { [$0] }
self.iOS = iOS.map { [$0] }
self.watchOS = watchOS.map { [$0] }
self.tvOS = tvOS.map { [$0] }
self.visionOS = visionOS.map { [$0] }
}
public init(macOS: Array<XcodeVersion>?, iOS: XcodeVersion? = nil, watchOS: XcodeVersion? = nil, tvOS: XcodeVersion? = nil, visionOS: XcodeVersion? = nil) {
self.macOS = macOS?.isEmpty == true ? nil : macOS
self.iOS = iOS.map { [$0] }
self.watchOS = watchOS.map { [$0] }
self.tvOS = tvOS.map { [$0] }
self.visionOS = visionOS.map { [$0] }
}
public init(macOS: Array<XcodeVersion>?, iOS: Array<XcodeVersion>?, watchOS: XcodeVersion? = nil, tvOS: XcodeVersion? = nil, visionOS: XcodeVersion? = nil) {
self.macOS = macOS?.isEmpty == true ? nil : macOS
self.iOS = iOS?.isEmpty == true ? nil : iOS
self.watchOS = watchOS.map { [$0] }
self.tvOS = tvOS.map { [$0] }
self.visionOS = visionOS.map { [$0] }
}
public init(macOS: Array<XcodeVersion>?, iOS: Array<XcodeVersion>?, watchOS: Array<XcodeVersion>?, tvOS: XcodeVersion? = nil, visionOS: XcodeVersion? = nil) {
self.macOS = macOS?.isEmpty == true ? nil : macOS
self.iOS = iOS?.isEmpty == true ? nil : iOS
self.watchOS = watchOS?.isEmpty == true ? nil : watchOS
self.tvOS = tvOS.map { [$0] }
self.visionOS = visionOS.map { [$0] }
}
public init(macOS: Array<XcodeVersion>?, iOS: Array<XcodeVersion>?, watchOS: Array<XcodeVersion>?, tvOS: Array<XcodeVersion>?, visionOS: Array<XcodeVersion>?) {
self.macOS = macOS?.isEmpty == true ? nil : macOS
self.iOS = iOS?.isEmpty == true ? nil : iOS
self.watchOS = watchOS?.isEmpty == true ? nil : watchOS
self.tvOS = tvOS?.isEmpty == true ? nil : tvOS
self.visionOS = visionOS?.isEmpty == true ? nil : visionOS
}
}

View file

@ -0,0 +1,35 @@
//
// Xcode.swift
// xcodereleases
//
// Created by Xcode Releases on 4/3/18.
// Copyright © 2018 Xcode Releases. All rights reserved.
//
import Foundation
public struct XcodeRelease: Codable {
public let name: String
public let version: XcodeVersion
public let date: YMD
public let requires: String
public let sdks: SDKs?
public let compilers: Compilers?
public let links: Links?
public let checksums: Checksums?
public var architectures: [Architecture]? {
return links.flatMap { $0.download?.architectures }
}
public init(name: String = "Xcode", version: XcodeVersion, date: (Int, Int, Int), requires: String, sdks: SDKs? = nil, compilers: Compilers? = nil, links: Links? = nil, checksums: Checksums? = nil) {
self.name = name
self.version = version;
self.date = YMD(date);
self.requires = requires;
self.sdks = sdks;
self.compilers = compilers
self.links = links
self.checksums = checksums
}
}

View file

@ -0,0 +1,24 @@
//
// Version.swift
// xcodereleases
//
// Created by Xcode Releases on 4/4/18.
// Copyright © 2018 Xcode Releases. All rights reserved.
//
import Foundation
public typealias V = XcodeVersion
public struct XcodeVersion: Codable {
public let number: String?
public let build: String?
public let release: Release
public init(_ build: String, _ number: String? = nil, _ release: Release = .release) {
self.number = number; self.build = build; self.release = release
}
public init(number: String, _ build: String? = nil, _ release: Release = .release) {
self.number = number; self.build = build; self.release = release
}
}

View file

@ -0,0 +1,23 @@
//
// YMD.swift
// xcodereleases
//
// Created by Xcode Releases on 4/4/18.
// Copyright © 2018 Xcode Releases. All rights reserved.
//
import Foundation
public struct YMD: Codable {
public let year: Int
public let month: Int
public let day: Int
public init(_ ymd: (Int, Int, Int)) {
self.year = ymd.0; self.month = ymd.1; self.day = ymd.2
}
public init(_ year: Int, _ month: Int, _ day: Int) {
self.year = year; self.month = month; self.day = day
}
}

View file

@ -22,9 +22,14 @@ public struct RuntimeService {
// Apple gives a plist for download // Apple gives a plist for download
let (data, _) = try await networkService.requestData(urlRequest, validators: []) let (data, _) = try await networkService.requestData(urlRequest, validators: [])
let decodedResponse = try PropertyListDecoder().decode(DownloadableRuntimesResponse.self, from: data) do {
let decodedResponse = try PropertyListDecoder().decode(DownloadableRuntimesResponse.self, from: data)
return decodedResponse return decodedResponse
} catch {
print("error: \(error)")
throw error
}
} }
public func installedRuntimes() async throws -> [InstalledRuntime] { public func installedRuntimes() async throws -> [InstalledRuntime] {

View file

@ -6,10 +6,14 @@ public typealias ProcessOutput = (status: Int32, out: String, err: String)
extension Process { extension Process {
static func run(_ executable: Path, workingDirectory: URL? = nil, input: String? = nil, _ arguments: String...) async throws -> ProcessOutput { static func run(_ executable: Path, workingDirectory: URL? = nil, input: String? = nil, _ arguments: String...) async throws -> ProcessOutput {
return try await run(executable.url, workingDirectory: workingDirectory, input: input, arguments) return try run(executable.url, workingDirectory: workingDirectory, input: input, arguments)
} }
static func run(_ executable: URL, workingDirectory: URL? = nil, input: String? = nil, _ arguments: [String]) async throws -> ProcessOutput { static func run(_ executable: Path, workingDirectory: URL? = nil, input: String? = nil, _ arguments: String...) throws -> ProcessOutput {
return try run(executable.url, workingDirectory: workingDirectory, input: input, arguments)
}
static func run(_ executable: URL, workingDirectory: URL? = nil, input: String? = nil, _ arguments: [String]) throws -> ProcessOutput {
let process = Process() let process = Process()
process.currentDirectoryURL = workingDirectory ?? executable.deletingLastPathComponent() process.currentDirectoryURL = workingDirectory ?? executable.deletingLastPathComponent()

Some files were not shown because too many files have changed in this diff Show more