Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/build-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ jobs:
- runner: macos-15-intel
xcode: 26.3
- runner: macos-26
xcode: 26.4
- runner: macos-26-intel
xcode: 26.4
runs-on: ${{matrix.runner}}
defaults:
run:
Expand All @@ -47,7 +49,7 @@ jobs:

- name: 🛠 Select Xcode version
if: matrix.xcode != 'swift'
run: xcodes select ${{matrix.xcode}}
run: sudo xcode-select -s /Applications/Xcode_${{matrix.xcode}}.app

- name: 👢 Bootstrap
run: Scripts/bootstrap
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
if: matrix.language == 'swift'
shell: bash
run: |
xcodes select
sudo xcode-select -s /Applications/Xcode_26.4.app
Scripts/build codeql -c release

- name: 🔍 Perform CodeQL analysis
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tag-pushed.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
fi

- name: 🛠 Select Xcode version
run: xcodes select
run: sudo xcode-select -s /Applications/Xcode_26.4.app

- name: 📦 Build Apple & Intel installers
run: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
/.idea/
/.swiftpm/
/.vscode/
/libexec/bin/mas
.DS_Store
*~
1 change: 1 addition & 0 deletions .swiftformat
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
--timezone utc
--type-attributes prev-line
--type-body-marks remove
--type-order beforeMarks,nestedType,staticProperty,staticPropertyWithBody,classPropertyWithBody,staticMethod,classMethod,overriddenProperty,swiftUIPropertyWrapper,instanceProperty,computedProperty,instanceLifecycle,swiftUIProperty,swiftUIMethod,overriddenMethod,instanceMethod
--wrap-arguments before-first
--wrap-collections before-first
--wrap-conditions before-first
Expand Down
4 changes: 2 additions & 2 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,16 @@ type_contents_order:
- type_alias
- subtype
- type_property
- type_method
- instance_property
- ib_inspectable
- ib_outlet
- initializer
- deinitializer
- type_method
- view_life_cycle_method
- ib_action
- other_method
- subscript
- other_method
unneeded_override:
affect_initializers: true
unused_import:
Expand Down
2 changes: 1 addition & 1 deletion .xcode-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
26.4
26.4.1
80 changes: 45 additions & 35 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
- Useful code locations & examples (look here for patterns to follow):
- CLI commands: `Sources/mas/Commands/` (e.g. `Install.swift`, `List.swift`)
- Models: `Sources/mas/Models/` (e.g. `AppID.swift`, `CatalogApp.swift`)
- Utilities: `Sources/mas/Utilities/` (e.g. `JSON/AnyJSONEncodable.swift`)
- Utilities: `Sources/mas/Utilities/` (e.g. `Output/Printer.swift`)
- Tests & test naming: `Tests/MASTests/` (see `MASTests+*.swift` files)
- Private framework headers: `Sources/PrivateFrameworks/include/CommerceKit/`
& `Sources/PrivateFrameworks/include/StoreFoundation/` (used via the
Expand All @@ -35,24 +35,29 @@

Do NOT refactor code if doing so makes the caller interface worse. Specifically:

- **Inline a utility function at a call site only if it is single-use**.
Inlining increases verbosity, introduces duplication bugs & makes code harder
to maintain. Keep clean abstractions. Example of what NOT to do:
- **Inline a utility function or computed var at a call site iff it is
single-use**. Inlining multi-use functions or computed vars increases
verbosity, introduces duplication bugs & makes code harder to maintain. Keep
clean abstractions. Example of what NOT to do:
```swift
// ❌ BAD: Inlining capitalizingFirstCharacter at each call site
action.performing.prefix(1).uppercased() + action.performing.dropFirst()
// ❌ BAD: Inlining capitalizingFirstCharacter at multiple call sites
action1.performing.prefix(1).uppercased() + action.performing.dropFirst()
action2.performing.prefix(1).uppercased() + action.performing.dropFirst()
// ✅ GOOD: Use the utility function
action.performing.capitalizingFirstCharacter
action1.performing.capitalizingFirstCharacter
action2.performing.capitalizingFirstCharacter
```
- **Never replace a clean, readable abstraction with a verbose closure**. e.g.,
if a custom `SortComparator` or similar exists & is used multiple times, keep
it. Only consider inlining if it's used in exactly one place. Example of what
NOT to do:
if a custom `SortComparator` or similar is used multiple times, keep it.
Consider inlining only if the abstraction is used in exactly one place.
Example of what NOT to do:
```swift
// ❌ BAD: Replacing a clean comparator with verbose closure
[].sorted { $0.compare($1, options: .numeric) == .orderedAscending }
[6, 9, 3].sorted { $0.compare($1, options: .numeric) == .orderedAscending }
[2, 8, 4].sorted { $0.compare($1, options: .numeric) == .orderedAscending }
// ✅ GOOD: Keep the abstraction
[].sorted(using: NumericStringComparator.forward)
[6, 9, 3].sorted(using: NumericStringComparator.forward)
[2, 8, 4].sorted(using: NumericStringComparator.forward)
```
- **Replace a utility call** only when the new calling interface is at least as
simple as the current calling interface
Expand Down Expand Up @@ -227,6 +232,8 @@ Source is organized by concern:

Each subsection contains code preferences in descending order.

Within this section & all subsections, `X` is a placeholder for any type name.

#### Naming

1. Standardized name
Expand All @@ -249,16 +256,20 @@ Each subsection contains code preferences in descending order.
#### Typing

1. Inferred type, e.g.:
- `var a = [String]()`
- `var o = Int?.none`
- `var c: String { .init(a.count) }`
- `var a = [X]()`
- `var o = X?.none`
- `var c: X { .init() }`
- `f(array: .init())`
- `f(dictionary: .init())`
2. Cast type, e.g.:
- `var a = [] as [String]`
- `var o = nil as Int?`
- `var a = [] as [X]`
- `var o = nil as X?`
3. Explicit type, e.g.:
- `var a: [String] = .init()`
- `var o: Int? = nil`
- `var c: String { String(a.count) }`
- `var a: [X] = .init()`
- `var o: X? = nil`
- `var c: X { X() }`
- `f(array: [])`
- `f(dictionary: [:])`

#### Functional

Expand All @@ -281,14 +292,14 @@ Each subsection contains code preferences in descending order.

1. Nil-coalescing operator (`??`)
2. Ternary operator
3. `Optional.map`/`flatMap`
3. `Optional.map(_:)` / `Optional.flatMap(_:)`
4. Single `guard`
5. `if`/`else` (no `else if`)
5. `if` / `else` (no `else if`)
6. `switch`
7. Multiple `guard`
8. `if`/`else if`…/`else`
8. `if` / `else if`… / `else`
9. Forced unwrapping (`!` suffix)
10. `fatalError()`
10. `fatalError(_:file:line:)`

#### Throwing

Expand Down Expand Up @@ -321,15 +332,15 @@ Each subsection contains code preferences in descending order.
#### Type Syntax

1. Concision:
- Generics: `<T: String>`
- Optional: `String?`
- Collection: `[String]`
- Dictionary: `[String:String]`
- Generics: `<T: X>`
- Optional: `X?`
- Collection: `[X]`
- Dictionary: `[X:X]`
2. Verbosity:
- Generics: `where T: String`
- Optional: `Optional<String>`
- Collection: `Array<String>`
- Dictionary: `Dictionary<String, String>`
- Generics: `where T: X`
- Optional: `Optional<X>`
- Collection: `Array<X>`
- Dictionary: `Dictionary<X, X>`

#### Void Types

Expand All @@ -343,7 +354,7 @@ Each subsection contains code preferences in descending order.

#### Closure Arguments

1. Shorthand argument names (e.g., `$0`) only for one-line closure
1. Shorthand argument names (e.g., `$0`) iff one-line closure
2. Explicit argument names for multi-line closure

#### Functional Arguments
Expand All @@ -366,6 +377,5 @@ Each subsection contains code preferences in descending order.
- Derive test file paths from source file paths:
- replace the `Sources/mas/` source path folder prefix with `Tests/MASTests/`
- prepend `MASTests+` to the source file name
- e.g., `Sources/mas/Models/Foo.swift` →
`Tests/MASTests/Models/MASTests+Foo.swift`
- e.g., `Sources/mas/X.swift` → `Tests/MASTests/MASTests+X.swift`
- Use force unwrapping (`!` suffix)
5 changes: 2 additions & 3 deletions Brewfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
brew "actionlint" # 1.7.12
brew "editorconfig-checker" # 3.6.1
brew "gh" # 2.90.0
brew "gh" # 2.91.0
brew "git" # 2.54.0
brew "ipsw" # 3.1.672
brew "markdownlint-cli2" # 0.22.0
brew "markdownlint-cli2" # 0.22.1
brew "periphery" if MacOS.version >= :sequoia && `/usr/bin/arch` == "arm64" # 3.7.2
brew "shellcheck" # 0.11.0
brew "swiftformat" # 0.61.0
brew "swiftlint" # 0.63.2
brew "xcodes" # 1.6.2
brew "yamllint" # 1.38.0
20 changes: 19 additions & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ private let swiftSettings = [
.enableUpcomingFeature("InternalImportsByDefault"),
.enableUpcomingFeature("MemberImportVisibility"),
.enableUpcomingFeature("NonisolatedNonsendingByDefault"),
.strictMemorySafety(),
.treatAllWarnings(as: .error),
]

Expand All @@ -23,6 +24,7 @@ _ = Package(
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.3.0"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.4.1"),
.package(url: "https://github.com/attaswift/BigInt.git", from: "5.7.0"),
.package(url: "https://github.com/mas-cli/swift-json.git", from: "3.3.0"),
.package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.13.4"),
],
targets: [
Expand All @@ -33,6 +35,7 @@ _ = Package(
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "Atomics", package: "swift-atomics"),
.product(name: "JSON", package: "swift-json"),
.product(name: "OrderedCollections", package: "swift-collections"),
"BigInt",
"PrivateFrameworks",
Expand Down
Loading
Loading