From 4d2b03e4b60cc9aec7228b2e3b85951a29631c62 Mon Sep 17 00:00:00 2001 From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:25:17 -0400 Subject: [PATCH 1/6] Cleanup code. Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> --- Sources/mas/AppStore/AppStoreAction+download.swift | 2 +- Sources/mas/Models/CatalogApp.swift | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Sources/mas/AppStore/AppStoreAction+download.swift b/Sources/mas/AppStore/AppStoreAction+download.swift index 29942fd1b..dc6b9fe92 100644 --- a/Sources/mas/AppStore/AppStoreAction+download.swift +++ b/Sources/mas/AppStore/AppStoreAction+download.swift @@ -87,7 +87,7 @@ private actor DownloadQueueObserver: CKDownloadQueueObserver { unsafe task = Task { [weak self] in for await event in eventStream { guard let self else { - break + return } switch event { diff --git a/Sources/mas/Models/CatalogApp.swift b/Sources/mas/Models/CatalogApp.swift index 4b8257ab1..f7c5b4107 100644 --- a/Sources/mas/Models/CatalogApp.swift +++ b/Sources/mas/Models/CatalogApp.swift @@ -324,9 +324,8 @@ func search(for searchTerm: String) async throws -> [CatalogApp] { private func search(for searchTerm: String, inRegion region: Region = appStoreRegion) async throws -> [CatalogApp] { let queryItem = URLQueryItem(name: "term", value: searchTerm) - let catalogApps = - try await catalogAppJSONObjects(from: Dependencies.current.searchURL, queryItem, inRegion: region) - .map { try CatalogApp(object: $0) } + let catalogApps = try await catalogAppJSONObjects(from: Dependencies.current.searchURL, queryItem, inRegion: region) + .map { try CatalogApp(object: $0) } let adamIDSet = Set(catalogApps.map(\.adamID)) return catalogApps.priorityMerge( try await catalogAppJSONObjects( @@ -365,7 +364,6 @@ private func catalogAppJSONObjects( private let minimumOSVersionKey = JSON.Key("minimumOsVersion") private let artworkURLRegex = /(?:^artworkUrl|ArtworkUrl)(\d+)/ -private let trackRegex = /((?:^track|Track)(?:Id)?)(s?)($|[\d\p{Upper}])/ -// editorconfig-checker-disable-next-line +private let trackRegex = /((?:^track|Track)(?:Id)?)(s?)($|[\d\p{Upper}])/ // editorconfig-checker-disable-next-line private let manyRegex = /(^appletv|Appletv|^artist|Artist|^artwork|Artwork|^genre|Genre|Id|^ipad|Ipad|Os|^releaseDate|Url|^view|View|Vpp)(s?)(?=$|[\d\p{Upper}])/ private let minimumOSVersionRegex = /macOS\s*(?\S+)/ From 315a04df318700535211e3017d51e17d072a9401 Mon Sep 17 00:00:00 2001 From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:55:43 -0400 Subject: [PATCH 2/6] Improve `Scripts/mas`. Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> --- Scripts/mas | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Scripts/mas b/Scripts/mas index aef8c31e3..4d2450c68 100755 --- a/Scripts/mas +++ b/Scripts/mas @@ -10,12 +10,7 @@ path_or_simple_command() { [[ -x "${1}" ]] && printf %s "${1}" || printf %s "${1:t}" } -# shellcheck disable=SC2311 -mas="$(path_or_simple_command "${0:A:h}/../libexec/bin/mas")" -readonly mas -# shellcheck disable=SC2311 -column="$(path_or_simple_command /usr/bin/column)" -readonly column +readonly mas="${0:A:h}/../libexec/bin/mas" # shellcheck disable=SC2311 jq="$(path_or_simple_command /usr/bin/jq)" readonly jq @@ -50,8 +45,8 @@ try ( "(" + $version'"${new_version_jq-}"' + ")" ]'"${output_price-}"' | join("\u001f") -) catch ("'"${error_prefix}"' Invalid data from mas: \(.)\n" | halt_error(1)) -' | "${column}" -ts $'\u001f' +) catch ("'"${error_prefix}"' Invalid JSON: \(.)\n" | halt_error(1)) +' | /usr/bin/column -ts $'\u001f' } 4>&1 5>&2 ;; lookup|info) @@ -92,7 +87,7 @@ try ( join("\n") ] | map(select(length > 0)) | if length > 0 then join("\n\n") else empty end -) catch ("'"${error_prefix}"' Invalid data from mas: \(.)\n" | halt_error(1)) +) catch ("'"${error_prefix}"' Invalid JSON: \(.)\n" | halt_error(1)) ' } 4>&1 5>&2 ;; @@ -103,7 +98,7 @@ try ( (keys_unsorted | map(length) | max) as $max_key_length | to_entries[] | "\(.key) \("▁" * ($max_key_length - (.key | length) + 1)) \(.value)" -) catch ("'"${error_prefix}"' Invalid data from mas: \(.)\n" | halt_error(1)) +) catch ("'"${error_prefix}"' Invalid JSON: \(.)\n" | halt_error(1)) ' } 4>&1 5>&2 ;; From d6d048a135486388e7aa6c70cb16796475d48546 Mon Sep 17 00:00:00 2001 From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> Date: Wed, 29 Apr 2026 23:26:09 -0400 Subject: [PATCH 3/6] Minimize permission scopes in GHA workflows. Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> --- .github/workflows/codeql.yaml | 1 + .github/workflows/release-published.yaml | 5 +++-- .github/workflows/tag-pushed.yaml | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 1151537ad..8433398c3 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -11,6 +11,7 @@ on: schedule: - cron: 44 14 * * 4 workflow_dispatch: {} +permissions: {} jobs: analyze: name: Analyze ${{matrix.language}} diff --git a/.github/workflows/release-published.yaml b/.github/workflows/release-published.yaml index a3ca6a1db..900bd3488 100644 --- a/.github/workflows/release-published.yaml +++ b/.github/workflows/release-published.yaml @@ -8,8 +8,6 @@ on: types: [published] permissions: actions: read - contents: write - pull-requests: write defaults: run: # Force all run commands to not use Rosetta 2 @@ -18,6 +16,9 @@ jobs: release-published: if: ${{!github.event.repository.fork}} runs-on: macos-26 + permissions: + contents: write + pull-requests: write steps: - name: 🛒 Checkout repo env: diff --git a/.github/workflows/tag-pushed.yaml b/.github/workflows/tag-pushed.yaml index c77ad0f18..ca9a0d2eb 100644 --- a/.github/workflows/tag-pushed.yaml +++ b/.github/workflows/tag-pushed.yaml @@ -6,8 +6,7 @@ name: tag-pushed on: push: tags: ['**'] -permissions: - contents: write +permissions: {} defaults: run: # Force all run commands to not use Rosetta 2 @@ -16,6 +15,8 @@ jobs: tag-pushed: if: ${{!github.event.repository.fork}} runs-on: macos-26 + permissions: + contents: write steps: - name: 🛒 Checkout repo env: From 8f17cc984e85e699e6321588c466330358e5887e Mon Sep 17 00:00:00 2001 From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> Date: Thu, 30 Apr 2026 00:00:58 -0400 Subject: [PATCH 4/6] Pin GHA actions to git revisions. Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> --- .github/workflows/build-test.yaml | 2 +- .github/workflows/codeql.yaml | 6 +++--- .github/workflows/release-published.yaml | 2 +- .github/workflows/tag-pushed.yaml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index 5537e2696..bb135bd25 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -39,7 +39,7 @@ jobs: GIT_CONFIG_COUNT: 1 GIT_CONFIG_KEY_0: init.defaultBranch GIT_CONFIG_VALUE_0: ${{github.event.repository.default_branch}} - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # Include all history & tags for Scripts/version fetch-depth: 0 diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 8433398c3..fbb2caa07 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -32,7 +32,7 @@ jobs: GIT_CONFIG_COUNT: 1 GIT_CONFIG_KEY_0: init.defaultBranch GIT_CONFIG_VALUE_0: ${{github.event.repository.default_branch}} - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # Include all history & tags for Scripts/version fetch-depth: 0 @@ -41,7 +41,7 @@ jobs: run: Scripts/setup_workflow_repo - name: 🔩 Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 with: languages: ${{matrix.language}} build-mode: ${{matrix.build-mode}} @@ -55,6 +55,6 @@ jobs: Scripts/build codeql -c release - name: 🔍 Perform CodeQL analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 with: category: /language:${{matrix.language}} diff --git a/.github/workflows/release-published.yaml b/.github/workflows/release-published.yaml index 900bd3488..3b3b795b5 100644 --- a/.github/workflows/release-published.yaml +++ b/.github/workflows/release-published.yaml @@ -25,7 +25,7 @@ jobs: GIT_CONFIG_COUNT: 1 GIT_CONFIG_KEY_0: init.defaultBranch GIT_CONFIG_VALUE_0: ${{github.event.repository.default_branch}} - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # Include all history & tags for Scripts/version fetch-depth: 0 diff --git a/.github/workflows/tag-pushed.yaml b/.github/workflows/tag-pushed.yaml index ca9a0d2eb..5a152442b 100644 --- a/.github/workflows/tag-pushed.yaml +++ b/.github/workflows/tag-pushed.yaml @@ -23,7 +23,7 @@ jobs: GIT_CONFIG_COUNT: 1 GIT_CONFIG_KEY_0: init.defaultBranch GIT_CONFIG_VALUE_0: ${{github.event.repository.default_branch}} - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # Include all history & tags for Scripts/version fetch-depth: 0 From 9e0d66606a2e6f672dda979c550e7351285b1115 Mon Sep 17 00:00:00 2001 From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> Date: Thu, 30 Apr 2026 06:25:46 -0400 Subject: [PATCH 5/6] Add `SECURITY.md`. Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> --- SECURITY.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..2f822467f --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,28 @@ +# Security Policy + +## Supported Versions + +Security (& other) updates are released only for the current version. + +## Vulnerability Reporting + +If you believe that you have discovered a security vulnerability, please report +it via [Security and quality](https://github.com/mas-cli/mas/security). + +Reports must include: + +- version(s) believed to be affected +- expected & observed behaviors +- steps that reproduce the vulnerability + +## Report Handling + +Any acknowledgment of a report is not necessarily an acceptance or rejection. + +You might be contacted for additional info, collaboration, and/or updates about +the status of any investigation and/or resolution. + +Resolution timelines will vary depending on complexity & severity. + +You will be credited whenever a valid report is published, unless you request +anonymity. From caec297147d3d0802a7a3857918e0e9b937e3f98 Mon Sep 17 00:00:00 2001 From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:59:21 -0400 Subject: [PATCH 6/6] Document outdated-app detection in `README.md`. Improve existing documentation in `README.md`, `AGENTS.md` & `GEMINI.md`. Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> --- AGENTS.md | 40 +++++++------- GEMINI.md | 68 ++++++++++++------------ README.md | 156 +++++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 186 insertions(+), 78 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index eb6fd3135..1f8a548b4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -67,9 +67,9 @@ Do NOT refactor code if doing so makes the caller interface worse. Specifically: ## Minimum Versions -- **Swift**: [6.2](.swift-version) -- **Xcode**: [26](.xcode-version) -- **macOS**: [13](Package.swift) +- **Swift:** [6.2](.swift-version) +- **Xcode:** [26](.xcode-version) +- **macOS:** [13](Package.swift) ## Distributions @@ -133,22 +133,22 @@ Scripts/package ## Git Workflow -- **Trunk-based development**: `main` is the trunk -- **Topic branches**: Branch from `main` (e.g., `git checkout -b feature main`) -- **Commit messages**: Follow [commit message conventions]( +- **Trunk-based development:** `main` is the trunk +- **Topic branches:** Branch from `main` (e.g., `git checkout -b feature main`) +- **Commit messages:** Follow [commit message conventions]( https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html ) -- **Release tags**: Format like `v1.2.3` -- **Before committing**: Run `Scripts/format` repeatedly until it no longer +- **Release tags:** Format like `v1.2.3` +- **Before committing:** Run `Scripts/format` repeatedly until it no longer modifies any files, then run `Scripts/lint`, then fix all violations ## Content Formatting -- **Newlines**: UNIX (i.e. `\n`) -- **Indentation**: Tabs (width: 2) -- **Max line length**: 120 characters -- **Unnecessary trailing whitespace**: Remove -- **File ends**: Single newline +- **Newlines:** UNIX (i.e. `\n`) +- **Indentation:** Tabs (width: 2) +- **Max line length:** 120 characters +- **Unnecessary trailing whitespace:** Remove +- **File ends:** Single newline ## Content Preservation @@ -199,8 +199,8 @@ The `PrivateFrameworks` SwiftPM target allows using the following Apple private frameworks (via Objective-C headers extracted from the DSC) to deploy App Store apps: -- **CommerceKit**: Controllers -- **StoreFoundation**: Models +- **CommerceKit:** Controllers +- **StoreFoundation:** Models Use them only where public APIs are insufficient. @@ -210,12 +210,12 @@ Apple-exclusive entitlements. ### Source Folder Hierarchy -Source is organized by concern: +Source is organized in folders by concern: -- **AppStore/**: App Store integration -- **Commands/**: CLI implementation -- **Models/**: Data types -- **Utilities/**: Utilities +- **AppStore:** App Store integration +- **Commands:** CLI implementation +- **Models:** Data types +- **Utilities:** Utilities ### Style Essentials diff --git a/GEMINI.md b/GEMINI.md index 250f166f9..5f6171fe0 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -5,19 +5,19 @@ development of `mas`, a command-line interface for the Mac App Store. ## Project Overview -- **Name**: `mas` -- **Description**: A CLI for the Mac App Store, designed for scripting & +- **Name:** `mas` +- **Description:** A CLI for the Mac App Store, designed for scripting & automation. -- **Language**: Swift 6.2 (using Swift Argument Parser) -- **Target OS**: macOS 13+ -- **Project Type**: SwiftPM project +- **Language:** Swift 6.2 (using Swift Argument Parser) +- **Target OS:** macOS 13+ +- **Project Type:** SwiftPM project ## Technical Stack -- **Swift**: 6.2+ (Check [.swift-version](.swift-version)) -- **Xcode**: 26+ (Check [.xcode-version](.xcode-version)) -- **macOS**: 13+ (Check [Package.swift](Package.swift)) -- **Private Frameworks**: Uses `CommerceKit` and `StoreFoundation` for App Store +- **Swift:** 6.2+ (Check [.swift-version](.swift-version)) +- **Xcode:** 26+ (Check [.xcode-version](.xcode-version)) +- **macOS:** 13+ (Check [Package.swift](Package.swift)) +- **Private Frameworks:** Uses `CommerceKit` and `StoreFoundation` for App Store integration only where public APIs are insufficient. ## Development Workflows @@ -51,54 +51,54 @@ Refer to [AGENTS.md](AGENTS.md) for comprehensive guidelines. Key highlights: ### Content Guidelines -- **Newlines**: UNIX (LF) -- **Indentation**: Tabs (width: 2) for Swift/Zsh; Spaces (2) for YAML; Spaces +- **Newlines:** UNIX (LF) +- **Indentation:** Tabs (width: 2) for Swift/Zsh; Spaces (2) for YAML; Spaces (1) for Markdown. -- **Max line length**: 120 characters (80 for Markdown). -- **Preservation**: Do not reformat, rename, or reorder code unless necessary +- **Max line length:** 120 characters (80 for Markdown). +- **Preservation:** Do not reformat, rename, or reorder code unless necessary for functionality. ### Markdown Guidelines -- **Style**: ATX headings, fenced code blocks with backticks, underscore +- **Style:** ATX headings, fenced code blocks with backticks, underscore emphasis, asterisk strong. -- **Language**: Only `console` and `shell` are allowed in code fences. -- **HTML**: Limited to `
`, `

`, ``. +- **Language:** Only `console` and `shell` are allowed in code fences. +- **HTML:** Limited to `
`, `

`, ``. ### YAML Guidelines -- **Style**: 2 spaces indentation, single quotes for strings, unix newlines. -- **Rules**: Forbid non-empty braces, require document start (`---`). +- **Style:** 2 spaces indentation, single quotes for strings, unix newlines. +- **Rules:** Forbid non-empty braces, require document start (`---`). ### Zsh Scripting -- **Shebang**: `#!/bin/zsh -Ndefgku` -- **Setup**: Start scripts with `. "${0:A:h}/_setup_script"` -- **Preference**: Use zsh builtins over external commands. -- **Commands**: Use `cp -c` and `trash` (not `rm`). +- **Shebang:** `#!/bin/zsh -Ndefgku` +- **Setup:** Start scripts with `. "${0:A:h}/_setup_script"` +- **Preference:** Use zsh builtins over external commands. +- **Commands:** Use `cp -c` and `trash` (not `rm`). ### Swift Development -- **Structure**: Organized by `AppStore/`, `Commands/`, `Models/`, `Utilities/`. -- **Force Unwrapping**: Avoid in `Sources/mas/`. -- **Naming**: Capitalize acronyms consistently (e.g., `ADAM`, `API`, `JSON`). -- **Organization**: Group computed properties below stored properties. -- **Error Handling**: Prefer typed throws (`throws(ErrorType)`), +- **Structure:** Organized by `AppStore/`, `Commands/`, `Models/`, `Utilities/`. +- **Force Unwrapping:** Avoid in `Sources/mas/`. +- **Naming:** Capitalize acronyms consistently (e.g., `ADAM`, `API`, `JSON`). +- **Organization:** Group computed properties below stored properties. +- **Error Handling:** Prefer typed throws (`throws(ErrorType)`), then `rethrows`, then untyped `throws`. ### Git Workflow -- **Trunk**: `main` -- **Commits**: Follow [conventional commit style]( +- **Trunk:** `main` +- **Commits:** Follow [conventional commit style]( https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html ). -- **Pre-commit**: Always run `Scripts/format` and `Scripts/lint` before +- **Pre-commit:** Always run `Scripts/format` and `Scripts/lint` before committing. ## Testing Requirements -- **Framework**: [Swift Testing](https://github.com/swiftlang/swift-testing). -- **Location**: `Tests/MASTests/` -- **Naming Convention**: `Sources/mas/Path/To/File.swift` -> +- **Framework:** [Swift Testing](https://github.com/swiftlang/swift-testing). +- **Location:** `Tests/MASTests/` +- **Naming Convention:** `Sources/mas/Path/To/File.swift` -> `Tests/MASTests/Path/To/MASTests+File.swift`. -- **Assertions**: Use force unwrapping (`!`) in tests if appropriate. +- **Assertions:** Use force unwrapping (`!`) in tests if appropriate. diff --git a/README.md b/README.md index 365da272c..777cfcad6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![current release version](https://img.shields.io/github/v/release/mas-cli/mas.svg?style=for-the-badge)](https://github.com/mas-cli/mas/releases/latest) +[![current version](https://img.shields.io/github/v/release/mas-cli/mas.svg?style=for-the-badge)](https://github.com/mas-cli/mas/releases/latest) [![supported OS: macOS 13+](https://img.shields.io/badge/Supported_OS-macOS_13%2B-teal?style=for-the-badge)](Package.swift) [![license: MIT](https://img.shields.io/badge/license-MIT-750014.svg?style=for-the-badge)](LICENSE) [![language: Swift 6.2](https://img.shields.io/badge/language-Swift_6.2-F05138.svg?style=for-the-badge)](https://www.swift.org) @@ -76,16 +76,16 @@ Detailed documentation is available via `man mas` & `mas --help`. -| Issue | Solution | -|:---------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Manage system software (macOS, Safari…) | Use [`softwareupdate`](https://www.unix.com/man-page/osx/8/softwareupdate) | -| [App info inconsistencies](https://github.com/mas-cli/mas/issues/387) | Wait hours – days (App Store uses eventual consistency) | -| [Cannot purchase paid apps](https://github.com/mas-cli/mas/issues/558) | Purchase paid apps directly in App Store; submit PR | -| [iOS & iPadOS apps unsupported](https://github.com/mas-cli/mas/issues/321) | Submit PR | -| [Hangs](https://github.com/mas-cli/mas/issues/1222) | [Index apps in Spotlight](#spotlight); [open bug report](https://github.com/mas-cli/mas/issues/new?template=01-bug-report.yaml) if hangs persist | -| Undetected installed apps | [Index apps in Spotlight](#spotlight) | -| `This redownload is not available for this Apple Account…` error | Sign in correct Apple Account to App Store, or uninstall app & get it with current Apple Account | -| Other bugs | [Subscribe to existing](https://github.com/mas-cli/mas/issues), or [open new](https://github.com/mas-cli/mas/issues/new?template=01-bug-report.yaml), bug report | +| Issue | Solution | +|:-------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Manage system software (macOS, Safari…) | Use [`softwareupdate`](https://www.unix.com/man-page/osx/8/softwareupdate) | +| [Inconsistent app data](https://github.com/mas-cli/mas/issues/387) | Wait hours – days (the App Store uses eventual consistency) | +| [Cannot purchase paid apps](https://github.com/mas-cli/mas/issues/558) | Purchase paid apps directly in the App Store; submit a PR | +| [iOS & iPadOS apps are unsupported](https://github.com/mas-cli/mas/issues/321) | Submit a PR | +| [Hangs](https://github.com/mas-cli/mas/issues/1222) | [Index apps in Spotlight](#spotlight); [open a bug report](https://github.com/mas-cli/mas/issues/new?template=01-bug-report.yaml) if hangs persist | +| Undetected installed apps | [Index apps in Spotlight](#spotlight) | +| `This redownload is not available for this Apple Account…` error | Sign in the correct Apple Account to the App Store, or uninstall the app & get it with the current Apple Account | +| Other bugs | [Subscribe to an existing](https://github.com/mas-cli/mas/issues), or [open a new](https://github.com/mas-cli/mas/issues/new?template=01-bug-report.yaml), bug report | @@ -120,15 +120,14 @@ considered bundle IDs. ADAM IDs can be found via: -1. `mas search …` -2. `mas list` -3. The App Store: - 1. Open an app's App Store page - 2. Open the page's Share Sheet - 3. Choose `Copy` - 4. Extract the ADAM ID from the URL in the copied text - - e.g., `497799835` from - +- `mas search …` +- `mas list` +- The App Store: + 1. Open an app's App Store page. + 2. Open the page's Share Sheet. + 3. Choose `Copy`. + 4. Extract the ADAM ID from the URL in the copied text. e.g., `497799835` from + `https://apps.apple.com/us/app/xcode/id497799835?mt=12`. ## JSON App Output @@ -180,8 +179,8 @@ Check if an app is properly indexed in Spotlight via: ```console ## General format: $ mdls -rn kMDItemAppStoreAdamID -## Outputs the ADAM ID if the app is indexed -## Outputs nothing if the app is not indexed +## Outputs the ADAM ID if the app is indexed. +## Outputs nothing if the app is not indexed. ## Example: $ mdls -rn kMDItemAppStoreAdamID /Applications/Xcode.app @@ -250,8 +249,117 @@ iTunes Store, App Store and Apple Books` is: each app being gotten. - `Never Require`: Apps are gotten without additional authentication. -> **Note:** App Store authentication is separate from any macOS user -> authentication required to grant root privileges to get apps. +**Note:** App Store authentication is separate from any macOS user +authentication required to grant root privileges to get apps. + +## Outdated-App Detection + +In `outdated` & `update`, the determination of whether an app is outdated is +configurable via 2 settings: + +- [Minimum macOS Check](#minimum-macos-check) +- [Accuracy](#accuracy) + +### Concepts & Constraints + +A **release** is a build of an app distributed to users. + +A **version** is a label that orders a release relative to all other releases of +the same app. + +The [iTunes Search API](https://performance-partners.apple.com/search-api) +reports app data only for the latest release of which it is aware; eventual +consistency delays can prevent the API from knowing about the latest release +available from the App Store. + +### Minimum macOS Check + +- `--check-min-os` (default): Filters outdated apps to include only those for + which the release reported by the + [iTunes Search API](https://performance-partners.apple.com/search-api) is + compatible with your current macOS. +- `--no-check-min-os`: Does not filter outdated apps. This is useful only when + multiple newer releases are available, with some compatible with your current + macOS, but the latest incompatible. With this setting, an app whose latest + release is newer than the installed release but incompatible with your current + macOS will: + - `outdated`: Be reported as outdated, which avoids false negatives, but could + cause false positives. + - `update`: Display a dialog offering to install the newest release compatible + with your current macOS, which might be the installed, or a newer (but not + the latest), release. + +### Accuracy + +The 2 outdated-app-detection modes are selectable via mutually exclusive flags: + + + +| Feature | `--inaccurate` (default) | `--accurate` | +|:-----------------|:---------------------------------------------------------------------------------|:---------------------------------------------| +| **Method** | Query the [iTunes Search API](https://performance-partners.apple.com/search-api) | Initiate App Store download to read metadata | +| **Accuracy** | Potential false positives or negatives | No false positives or negatives | +| **Speed** | Fast (~7ms per app) | Slow (~175ms per app) | +| **Requirements** | [iTunes Search API](https://performance-partners.apple.com/search-api) | Apple Account signed in to the App Store | +| **Dialogs** | Only if `--no-check-min-os` | Various potential dialogs | +| **Hangs** | None | If checking 100+ apps in quick succession | + + + +#### `--inaccurate` (default) + +The inaccurate mode is optimized to: + +- Be fast. +- Avoid hangs. +- Avoid dialogs (as long as `--no-check-min-os` is not supplied). +- Report outdated apps owned by any Apple Account without requiring an Apple + Account signed in to the App Store (apps can be updated, however, only for an + Apple Account signed in to the App Store). + +It compares an installed app's version with the version reported by the +[iTunes Search API](https://performance-partners.apple.com/search-api) as +[Semantic Versions](https://semver.org), with build metadata adjudicating ties. + +This mode suffers from potential false positives & negatives: + +- **Inconsistent versions:** For certain apps, the iTunes Search API + consistently reports a different version for the same release than is reported + by all other App Store systems & by installed apps, causing false positives or + negatives. +- **Eventual consistency delays:** Lags between the App Store & the iTunes + Search API can cause false negatives. + +#### `--accurate` + +The accurate mode is optimized to avoid false positives & negatives at the +expense of speed, potential hangs & potential dialogs. + +It initiates a download of an installed app's latest release from the App Store +to obtain the correct latest version from the download metadata. Subsequent +behavior depends on the command: + +- `outdated`: Each download is immediately cancelled; an app is reported as + outdated if its version differs from the download metadata version. +- `update`: The download is cancelled iff the installed version is the same as + the download metadata version. Otherwise, the update is allowed to complete. + +This mode: + +- Connects to the + [iTunes Search API](https://performance-partners.apple.com/search-api) iff + `--no-check-min-os` is not supplied. +- Requires an Apple Account signed in to the App Store. +- Opens a dialog: + - If no Apple Account is signed in to the App Store. + - For each app owned by a different Apple Account. + - For each app no longer available from the App Store. + - If `--no-check-min-os` is supplied, for each app for which the release + reported by the iTunes Search API is incompatible with your current macOS. +- Can hang: Initiating downloads for dozens of apps is safe, but for hundreds of + apps can cause the App Store to stop responding. This is likely due to Apple + rate limiting, for which the exact thresholds, durations & ramifications are + currently unknown. ## License