Commit Graph

22 Commits

  • anisette: fix crash in date formatter (#422)
    ## What does this PR do?
    
    Password login can terminate with an illegal-instruction crash on Linux,
    leaving the user logged out. Anisette client time was formatted with
    DateFormatter and an explicitly assigned Gregorian calendar. That
    formatting path reaches Foundation’s NSCalendar bridging code, where the
    reported crash occurs.
    
    AnisetteData now formats client time with Date.ISO8601FormatStyle,
    avoiding the DateFormatter calendar bridge. The header retains its UTC
    timestamp format with whole seconds and a trailing Z. New parameterized
    tests check the complete header dictionary for the Unix epoch, a year
    boundary, leap day, and a timestamp with fractional seconds.
    
    Fixes #184
    
    ## How was it tested?
    
    Ran `swift test` locally on macOS for the xtool AnisetteData change; it
    fails the same way on the base branch, so the failure predates this
    change (it fails the same way on upstream/main).
    `swift build` failed because an async call in a defer body prevented
    compilation.
    `swift test` failed during compilation with the same async-in-defer
    error, so the tests did not run.
    `repo_lint` ran without reported output.
    `make lint SWIFTLINT_FLAGS='--reporter=github-actions-logging' | tee
    swiftlint.log` ran with no lint diagnostics shown in the transcript.
    
    The tests in `Tests/XToolTests/AnisetteDataTests.swift` fail without the
    fix in the xtool AnisetteData change (checked by reverting the source
    changes).
    
    ## AI tool usage
    
    How much of the code in this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI in writing code. I reviewed the
    finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    AI was used for assistance.
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **Bug Fixes**
    * Client timestamps now use a consistent UTC ISO 8601 format with dashed
    dates, colon-separated times, and no fractional seconds. This applies to
    timestamps with or without fractional seconds in the source date.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
    
    ---------
    
    Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
  • utils: support forwarding Subprocess output (#413)
    ## What does this PR do?
    
    When spawning a subprocess,
    `currentStandardOutput`/`currentStandardError` bypass `d.out`. Add
    `.xtoolOutput()` and `.xtoolError()` which respect the dependency.
    
    ## How was it tested?
    
    Tested in a downstream PR to ensure that forwarding works
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **New Features**
    * Subprocess output can now be routed directly to XTool’s standard
    output or standard error.
    * Added a way to create a writable output descriptor that forwards data
    to a selected stream.
    * Asynchronous input reading is now available through a public
    interface, including configurable read sizes and explicit closing.
    
    * **Compatibility**
      * Existing input-reading behavior and parameters remain unchanged.
    
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • ds: fix assigning multiple app groups (#403)
    ## The bug
    
    `DeveloperServicesAssignAppGroupsOperation` sends one
    `assignApplicationGroupToAppId` request per app group, concurrently.
    That endpoint replaces the App ID's groups with the ones in the request,
    so an app whose entitlements list two or more groups ends up with only
    one of them on the App ID: whichever request Apple handles last. The
    provisioning profile then carries one group while the signed binary
    claims two, and the device refuses the install with
    `ApplicationVerificationFailed`.
    
    Setting the groups by hand in the developer portal does not help,
    because the next `xtool dev build --sign` sends the per-group requests
    again.
    
    ## The fix
    
    - `DeveloperServicesAssignAppGroupRequest` takes `groupIDs:
    [DeveloperServicesAppGroup.ID]` and sends `applicationGroups` as an
    array. The endpoint accepts an array; it was being given a single string
    before.
    - `DeveloperServicesAssignAppGroupsOperation.perform()` still finds or
    creates each group concurrently, then sends a single assign request
    carrying every group ID, and returns the group IDs in the order the
    entitlements listed them, so the rewritten entitlement is stable between
    builds.
    
    The only caller, `DeveloperServicesAddAppOperation`, is unchanged.
    
    ## Related
    
    #324 would report the resulting mismatch between the entitlements and
    the profile as a clear error at signing time; this change removes the
    cause, so an app with several groups gets a profile that grants all of
    them.
    
    ## Checked
    
    Built on Linux with the Swift 6.4 toolchain and used to sign an iOS app
    whose main target has two app groups and a Share extension. The app's
    `embedded.mobileprovision` now carries both groups and the device
    installs the app. The same app signed with the unpatched build got one
    group in the profile and `ApplicationVerificationFailed` on install.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    https://claude.ai/code/session_01UKzxgxaLwK7Ap5LMwj9MxH
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **New Features**
    * Apps can now be assigned multiple app groups in a single operation.
    The assignment replaces the app’s existing group associations with the
    selected groups, so any groups not included in the new assignment are no
    longer associated with the app.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
    
    ---------
    
    Co-authored-by: David Reinecke <david.reinecke@gmail.com>
    Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
    Co-authored-by: Kabir Oberai <oberai.kabir@gmail.com>
  • cli: better IO (#375)
    ## What does this PR do?
    
    Adds `Dependency`s for console IO. Also fixes #268 by explicitly
    handling non-pipe input.
    
    ## How was it tested?
    
    Ran on macOS + Linux in Docker. I have a future PR that overrides the
    dependencies in certain cases, which also appears to work correctly.
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    Did not scrutinize the tests too much tbh but they look reasonable. Easy
    enough to update/remove them in the future if needed.
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    <!-- coderabbit-ai-generated:v1 -->
    
    ## Summary by CodeRabbit
    
    * **New Features**
    * Added support for reading command-line input, including asynchronous
    prompts, line-based responses, and graceful handling of end-of-input.
      * Added coordinated cleanup when commands finish.
    * **Improvements**
    * Command messages across authentication, device management,
    installation, SDK, and other workflows now use consistent output
    handling; displayed messages and command behavior remain unchanged.
    * Improved handling of complete output when the system temporarily
    cannot accept more data.
    * **Tests**
    * Added coverage for command input and output, including stream
    behavior, cancellation, and shutdown.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • certs: match certificates whose serial starts with 0 (#306)
    ## What does this PR do?
    
    Fixes #305 (thanks @0x53 for the detailed diagnosis).
    
    `Certificate.serialNumber()` stripped leading `0` *characters* from the
    hex string, so a serial like `0337…` became `337…` and never matched the
    `serialNumber` returned by the API. xtool then treated its stored
    certificate as missing: free teams are prompted to revoke a valid
    certificate on every run, and paid teams take the same path and try to
    create another certificate, which @McoreD confirmed fails with `409 …
    You already have a current Development certificate or a pending
    certificate request.`
    
    The character-level `drop` predates b6a40ca, which switched to
    fixed-width `%02hhX` formatting; since then it removes the significant
    zero of any first byte below `0x10`. swift-certificates already strips
    leading zero bytes (including DER sign padding) when parsing, so the
    `drop` isn't needed for padding.
    
    - `serialNumber()` now drops only leading zero *bytes*, matching the API
    and `openssl x509 -serial`.
    - The stored-certificate lookup uses a new
    `Certificate.hasSerialNumber(_:)`, which compares serials numerically
    (ignoring case and leading zeros), so it doesn't depend on Apple's exact
    formatting.
    - Profile creation no longer finds the signing certificate with
    `filter[serialNumber]`. The API lists serials with the leading zero
    (`0E41…`), but the filter only matches them without it (`E41…`), so once
    `serialNumber()` kept the zero the filter matched nothing and profile
    creation failed with a 409
    ([details](https://github.com/xtool-org/xtool/pull/306#issuecomment-6181157860)).
    It now fetches all pages of certificates and matches them with
    `hasSerialNumber(_:)`. Fix by @McoreD, with pagination added so a
    certificate on a later page isn't missed.
    
    ## How was it tested?
    
    - Added `Tests/XToolTests/CertificateTests.swift`: builds a cert with
    the serial from #305, round-trips it through DER, and checks
    `serialNumber()` and `hasSerialNumber(_:)`, plus a high-bit serial so
    DER sign padding doesn't leak into the string. The leading-zero test
    fails on `main` (returns `33742570D6F9684A23311711956A084`) and passes
    with this change.
    - `swift test` (17 tests) and `make lint` pass on macOS (Xcode 27, Swift
    6.4, `XTL_CI=1`).
    - @McoreD tested on Linux (Arch, Swift 6.4.1, Xcode 27 SDK, on top of
    1.22.2) with a paid team whose certificate serial starts with `0`: the
    stored certificate is reused, and provisioning, signing and install to
    an iPhone (iOS 27) succeed end to end (tested before pagination was
    added). `CertificateTests` also pass on Linux.
    - Not tested with a free team.
    
    To generate test certificates, the test target now depends on `Crypto`
    and `X509`, which XKit already uses.
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    ## Summary by CodeRabbit
    
    * **Bug Fixes**
    * Certificate serial numbers now preserve leading zero nibbles while
    omitting unnecessary padding.
    * Certificate matching now ignores letter case and leading zeros, and
    profile lookups find matching certificates even when the API’s
    serial-number filter treats leading-zero values differently.
    * Certificate lookups continue to use the existing fallback when no
    matching certificate is found.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
    
    ---------
    
    Co-authored-by: McoreD <McoreD@users.noreply.github.com>
    Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
  • packer: custom SwiftVersion parser (#303)
    ## What does this PR do?
    
    Drops `mxcl/Version` in favour of a lighter parser that's specifically
    designed to parse the Swift version.
    
    ## How was it tested?
    
    - [x] Unit tests
    - [x] CI
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    
    ## Summary by CodeRabbit
    
    * **New Features**
    * Swift versions can now be compared by major, minor, and patch number,
    and displayed in a consistent three-part format.
    * Version parsing now accepts two- or three-number versions, including
    supported compiler-output formats with optional prefixes or a `-dev`
    suffix.
    * Versions with an omitted patch number are treated as equivalent to the
    same version with patch `0`.
    
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • tests: derive CFBundleSupportedPlatforms from the build triple (#289)
    ## What does this PR do?
    
    Application packing now sets CFBundleSupportedPlatforms from
    Packer.cfBundleSupportedPlatforms(for:). The helper splits the triple on
    hyphens, takes the OS token after apple with any numeric version
    stripped, and returns iPhoneSimulator for an ios triple that also
    contains simulator, iPhoneOS for other ios triples, MacOSX for macos or
    macosx, and iPhoneOS when the triple is missing or unrecognized.
    PackerTests covers those triples. Package.resolved drops the
    swift-issue-reporting pin and adds xctest-dynamic-overlay 1.13.1.
    
    Application builds aimed at the iOS simulator still ship an Info.plist
    whose CFBundleSupportedPlatforms value is iPhoneOS. Hosts that read that
    key treat the archive as a physical-device build and refuse to launch it
    on a simulator. While packing an application, Packer assigns
    CFBundleSupportedPlatforms the fixed array iPhoneOS. The build triple is
    already available on buildSettings, but that assignment never reads it,
    so a simulator triple is packed the same way as a device triple.
    
    Fixes #191
    
    ## How was it tested?
    
    The package builds successfully.
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    AI was used for assistance.
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    
    ## Summary by CodeRabbit
    
    * **Bug Fixes**
    * Application metadata now reports the supported platform based on the
    build target, including iOS devices, iOS simulators, and macOS. Unknown
    targets retain the iOS device platform as a fallback.
    * **Tests**
    * Added coverage for platform metadata across supported and unknown
    build targets.
    
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
    
    Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
  • package: bump deps (#281)
    ## What does this PR do?
    
    Bumps pointfree deps and explicitly disables OpenCombine so that we
    don't have different dependency graphs when building xtool on Darwin vs
    Linux hosts.
    
    ## How was it tested?
    
    A bunch of `swift package resolve`s
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    ## Summary by CodeRabbit
    
    * **Chores**
    * Updated supporting package versions and refreshed command-line and
    file-operation handling.
    * These changes do not add or remove app features or alter its public
    interface. No action is needed to continue using the app.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • tests: add integration tests (#252)
    ## What does this PR do?
    
    Adds integration tests
    
    ## How was it tested?
    
    CI
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [x] **0** - No AI was used to write code
    - [ ] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    
    ## Summary by CodeRabbit
    
    * **Tests**
    * Added integration smoke tests covering version checks, app creation,
    iOS build metadata, and slim SDK update behavior.
    * Added validation for locating Xcode and verifying the Swift toolchain.
    
    * **Chores**
    * Added automated integration-test execution for pushes and pull
    requests.
    * Added Docker and Compose support for running integration tests in a
    consistent environment.
    
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • sdk: self-updating darwin tools (#110)
    ## What does this PR do?
    
    Allows xtool to auto-update the Darwin SDK format when there's a new
    Darwin toolset, or when we change the SDK format. Downstream this also
    allows us to auto-update when there's changes to OpenAppleMacros (#101).
    
    One callout: the disk footprint of `darwin.artifactbundle` is now ~7 GB
    instead of ~3 GB.
    
    Reason: `xtool sdk install /path/to/Xcode.[app|xip]` now performs a
    "normal" install by default, which preserves the entirety of
    `Xcode.app/Contents/Developer` — even the bits we don't currently use,
    like `AppleTVOS.platform`. This makes it so if we start using more
    components in the future, the user doesn't need to download Xcode again
    and rebuild the SDK themselves.
    
    You can still build an old-style SDK (containing just the components we
    need) using `xtool sdk install --slim` or the dedicated `xtool sdk
    build`.
    
    ## How was it tested?
    
    Tested all of the install modes. Confirmed that auto-updating works.
    Added integration tests in #252.
    
    ## AI tool usage
    
    How much of this PR was AI-assisted? (check one)
    
    - [ ] **0** - No AI was used to write code
    - [x] **1** - I was assisted by AI. I reviewed the finished result.
    - [ ] **2** - I set the AI going and left it to it; nobody has read the
    result - no review, or AI review only
    
    <!-- If an AI agent is filling this in: declare the level honestly, and
    open as a draft if it is #2. Do not lower the declared level to get the
    PR reviewed. -->
    
    
    <!-- This is an auto-generated comment: release notes by coderabbit.ai
    -->
    ## Summary by CodeRabbit
    
    - **New Features**
    - Added SDK flavor reporting for slim, normal, and legacy installations.
    - Added `sdk update` support to rebuild and install SDKs from an
    installed Xcode bundle.
      - Added a `--slim` option for SDK installation.
      - SDK status now displays installation path, flavor, and version.
    - SDK setup and build operations now ensure a suitable SDK is available
    automatically.
    
    - **Bug Fixes**
    - Improved temporary SDK bundle handling, validation, replacement, and
    cross-filesystem copying.
      - Copy failures now provide detailed diagnostic information.
    <!-- end of auto-generated comment: release notes by coderabbit.ai -->