[GH-ISSUE #565] Profiles: add local install/list/cleanup commands for provisioning profiles #156

Closed
opened 2026-02-26 21:33:49 +03:00 by kerem · 0 comments
Owner

Originally created by @rudrankriyam on GitHub (Feb 16, 2026).
Original GitHub issue: https://github.com/rudrankriyam/App-Store-Connect-CLI/issues/565

Summary

Add first-class commands for local provisioning profile management:

  • install .mobileprovision files into the standard Xcode location
  • list installed profiles with parsed metadata
  • clean up expired / unwanted profiles with safe, non-interactive semantics

This is explicitly about local disk state, not App Store Connect API resources.

Why this matters

Provisioning profiles are frequently the hidden source of build/signing failures:

  • CI machines need repeatable profile installation (no Xcode UI steps).
  • Developers end up with stale/expired profiles that cause confusing signing resolution.
  • A deterministic, inspectable local profile inventory is valuable for agents (preflight + remediation).

Current state (verified)

  • asc profiles download --id "PROFILE_ID" --output "./profile.mobileprovision" exists.
  • asc signing fetch writes .mobileprovision files into an output directory.
  • There is no asc profiles install or any command that targets:
    • ~/Library/MobileDevice/Provisioning Profiles/
    • local listing/cleanup of installed profiles

Proposed UX

Introduce a local subcommand group to avoid ambiguity with API-backed profiles list/get/...:

  • asc profiles local install
  • asc profiles local list
  • asc profiles local clean

Install

# Install a downloaded profile file
asc profiles local install --path "./MyProfile.mobileprovision"

# Download from ASC then install (single step)
asc profiles local install --id "PROFILE_ID"

# Replace existing installed profile (explicit)
asc profiles local install --path "./MyProfile.mobileprovision" --force

List

asc profiles local list --output json
asc profiles local list --bundle-id "com.example.app" --output table
asc profiles local list --expired --output json

Clean

# Show plan only
asc profiles local clean --expired --dry-run

# Actually delete expired profiles
asc profiles local clean --expired --confirm

Flags (proposal)

Shared:

  • --output json|table|markdown (default json)
  • --pretty

install:

  • --path local .mobileprovision path (optional if --id is used)
  • --id profile ID to download then install (optional if --path is used)
  • --install-dir override install directory (default: ~/Library/MobileDevice/Provisioning Profiles)
  • --force overwrite if the target UUID already exists (default false)

list:

  • --install-dir (same default)
  • --bundle-id filter (best-effort; derived from entitlements)
  • --team-id filter
  • --expired show only expired

clean:

  • --install-dir
  • --expired (phase 1)
  • --dry-run show deletions without mutating disk
  • --confirm required for deletion

Behavior requirements

  • No interactive prompts.
  • Install uses the canonical filename convention: <UUID>.mobileprovision.
  • Safe file operations:
    • refuse to follow symlinks in the install dir
    • refuse overwrite by default; require --force
    • write atomically where possible
  • Non-darwin behavior:
    • if --install-dir is not provided and OS is not macOS, return a clear, deterministic error
    • if --install-dir is provided, allow operation (useful for CI/tests), but document that the default path is macOS-specific

Parsing requirements

To support list/filtering and correct install naming, parse .mobileprovision:

  • decode CMS/PKCS7 container
  • extract embedded plist
  • parse key fields:
    • UUID, Name, TeamIdentifier, ExpirationDate, CreationDate
    • best-effort derive bundle id / application identifier from Entitlements

Implementation should not require Keychain UI.

Output model

Install

Return a structured summary:

  • uuid, name, teamId, expiresAt
  • source (path or profile ID)
  • installedPath
  • action: installed, replaced, already_present, skipped

List

Return an array of installed profiles with the parsed metadata above + the on-disk path.

Clean

Return:

  • deleted[] (paths + uuid/name)
  • skipped[] (reason)
  • counts

Detailed implementation plan (TDD-first)

  • Add profiles local subcommand group under internal/cli/profiles/.
  • Implement profile parsing helper (CMS -> plist -> struct). Prefer a pure-Go approach so tests can run cross-platform.
    • Use howett.net/plist (already in repo) for plist decode.
    • Use a small PKCS7/CMS decoder dependency or equivalent.
  • Implement install:
    • validate input
    • parse UUID
    • compute destination path
    • write securely (--force gating)
  • Implement list:
    • scan install dir
    • parse each profile
    • apply filters
    • deterministic ordering (e.g., by expiration date, then UUID)
  • Implement clean:
    • build deletion plan
    • --dry-run prints plan
    • --confirm required to delete
  • Add cmdtests:
    • install writes <UUID>.mobileprovision into --install-dir temp dir
    • list returns deterministic JSON and supports filters
    • clean requires --confirm and respects --dry-run

Acceptance criteria

  • asc profiles local install|list|clean --help exists and is self-documenting.
  • Installs profiles with safe defaults (no overwrite unless --force).
  • Deletions require --confirm.
  • Outputs are deterministic and JSON-first.
  • make test passes with new cmdtest coverage.
Originally created by @rudrankriyam on GitHub (Feb 16, 2026). Original GitHub issue: https://github.com/rudrankriyam/App-Store-Connect-CLI/issues/565 ## Summary Add first-class commands for **local** provisioning profile management: - install `.mobileprovision` files into the standard Xcode location - list installed profiles with parsed metadata - clean up expired / unwanted profiles with safe, non-interactive semantics This is explicitly about **local disk state**, not App Store Connect API resources. ## Why this matters Provisioning profiles are frequently the hidden source of build/signing failures: - CI machines need repeatable profile installation (no Xcode UI steps). - Developers end up with stale/expired profiles that cause confusing signing resolution. - A deterministic, inspectable local profile inventory is valuable for agents (preflight + remediation). ## Current state (verified) - `asc profiles download --id "PROFILE_ID" --output "./profile.mobileprovision"` exists. - `asc signing fetch` writes `.mobileprovision` files into an output directory. - There is no `asc profiles install` or any command that targets: - `~/Library/MobileDevice/Provisioning Profiles/` - local listing/cleanup of installed profiles ## Proposed UX Introduce a `local` subcommand group to avoid ambiguity with API-backed `profiles list/get/...`: - `asc profiles local install` - `asc profiles local list` - `asc profiles local clean` ### Install ```bash # Install a downloaded profile file asc profiles local install --path "./MyProfile.mobileprovision" # Download from ASC then install (single step) asc profiles local install --id "PROFILE_ID" # Replace existing installed profile (explicit) asc profiles local install --path "./MyProfile.mobileprovision" --force ``` ### List ```bash asc profiles local list --output json asc profiles local list --bundle-id "com.example.app" --output table asc profiles local list --expired --output json ``` ### Clean ```bash # Show plan only asc profiles local clean --expired --dry-run # Actually delete expired profiles asc profiles local clean --expired --confirm ``` ## Flags (proposal) Shared: - `--output json|table|markdown` (default json) - `--pretty` `install`: - `--path` local `.mobileprovision` path (optional if `--id` is used) - `--id` profile ID to download then install (optional if `--path` is used) - `--install-dir` override install directory (default: `~/Library/MobileDevice/Provisioning Profiles`) - `--force` overwrite if the target UUID already exists (default false) `list`: - `--install-dir` (same default) - `--bundle-id` filter (best-effort; derived from entitlements) - `--team-id` filter - `--expired` show only expired `clean`: - `--install-dir` - `--expired` (phase 1) - `--dry-run` show deletions without mutating disk - `--confirm` required for deletion ## Behavior requirements - **No interactive prompts**. - Install uses the canonical filename convention: `<UUID>.mobileprovision`. - Safe file operations: - refuse to follow symlinks in the install dir - refuse overwrite by default; require `--force` - write atomically where possible - Non-darwin behavior: - if `--install-dir` is not provided and OS is not macOS, return a clear, deterministic error - if `--install-dir` is provided, allow operation (useful for CI/tests), but document that the default path is macOS-specific ## Parsing requirements To support list/filtering and correct install naming, parse `.mobileprovision`: - decode CMS/PKCS7 container - extract embedded plist - parse key fields: - `UUID`, `Name`, `TeamIdentifier`, `ExpirationDate`, `CreationDate` - best-effort derive bundle id / application identifier from `Entitlements` Implementation should not require Keychain UI. ## Output model ### Install Return a structured summary: - `uuid`, `name`, `teamId`, `expiresAt` - `source` (path or profile ID) - `installedPath` - `action`: `installed`, `replaced`, `already_present`, `skipped` ### List Return an array of installed profiles with the parsed metadata above + the on-disk path. ### Clean Return: - `deleted[]` (paths + uuid/name) - `skipped[]` (reason) - counts ## Detailed implementation plan (TDD-first) - [ ] Add `profiles local` subcommand group under `internal/cli/profiles/`. - [ ] Implement profile parsing helper (CMS -> plist -> struct). Prefer a pure-Go approach so tests can run cross-platform. - Use `howett.net/plist` (already in repo) for plist decode. - Use a small PKCS7/CMS decoder dependency or equivalent. - [ ] Implement install: - validate input - parse UUID - compute destination path - write securely (`--force` gating) - [ ] Implement list: - scan install dir - parse each profile - apply filters - deterministic ordering (e.g., by expiration date, then UUID) - [ ] Implement clean: - build deletion plan - `--dry-run` prints plan - `--confirm` required to delete - [ ] Add cmdtests: - install writes `<UUID>.mobileprovision` into `--install-dir` temp dir - list returns deterministic JSON and supports filters - clean requires `--confirm` and respects `--dry-run` ## Acceptance criteria - [ ] `asc profiles local install|list|clean --help` exists and is self-documenting. - [ ] Installs profiles with safe defaults (no overwrite unless `--force`). - [ ] Deletions require `--confirm`. - [ ] Outputs are deterministic and JSON-first. - [ ] `make test` passes with new cmdtest coverage.
kerem closed this issue 2026-02-26 21:33:49 +03:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/App-Store-Connect-CLI#156
No description provided.