[GH-ISSUE #52] Add TestFlight config pull (YAML) #13

Closed
opened 2026-02-26 21:32:47 +03:00 by kerem · 3 comments
Owner

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

Summary

Add a testflight sync pull command that exports TestFlight configuration as YAML for a given app.

API endpoints

CLI

  • asc testflight sync pull --app "APP_ID" --output "./testflight.yaml"
  • Optional filters: --group, --build, --tester for partial export

YAML schema (proposal)

  • app: id, name, bundleId
  • groups: id, name, isInternalGroup, publicLinkEnabled, publicLinkLimit*, feedbackEnabled, builds[]
  • builds: id, version, uploadedDate, processingState, groups[]
  • testers: id, email, name, state, groups[]

Output

  • YAML file written to disk
  • JSON summary on stdout (paths + counts)

Tests

  • YAML serializer tests
  • CLI tests for output path and filtering
  • Client tests for include handling
Originally created by @rudrankriyam on GitHub (Jan 23, 2026). Original GitHub issue: https://github.com/rudrankriyam/App-Store-Connect-CLI/issues/52 ## Summary Add a `testflight sync pull` command that exports TestFlight configuration as YAML for a given app. ## API endpoints - `GET /v1/apps/{id}` (app info) — https://developer.apple.com/documentation/appstoreconnectapi/read_app_information - `GET /v1/apps/{id}/betaGroups` (groups + build assignments) — https://developer.apple.com/documentation/appstoreconnectapi/list_beta_groups - `GET /v1/apps/{id}/builds` (builds + group assignments) — https://developer.apple.com/documentation/appstoreconnectapi/list_builds - `GET /v1/apps/{id}/betaTesters` (testers + group memberships) — https://developer.apple.com/documentation/appstoreconnectapi/list_beta_testers ## CLI - `asc testflight sync pull --app "APP_ID" --output "./testflight.yaml"` - Optional filters: `--group`, `--build`, `--tester` for partial export ## YAML schema (proposal) - `app`: id, name, bundleId - `groups`: id, name, isInternalGroup, publicLinkEnabled, publicLinkLimit*, feedbackEnabled, builds[] - `builds`: id, version, uploadedDate, processingState, groups[] - `testers`: id, email, name, state, groups[] ## Output - YAML file written to disk - JSON summary on stdout (paths + counts) ## Tests - YAML serializer tests - CLI tests for output path and filtering - Client tests for include handling
kerem closed this issue 2026-02-26 21:32:47 +03:00
Author
Owner

@rudrankriyam commented on GitHub (Jan 23, 2026):

@cursor Please implement this issue.

Implementation Guide

This is an advanced feature that exports TestFlight config to YAML. It requires aggregating multiple API calls.

1. Create cmd/testflight_sync.go

// TestFlightSyncCommand returns the testflight sync command group.
func TestFlightSyncCommand() *ffcli.Command {
    // Subcommand: pull (push can be added later)
}

func TestFlightSyncPullCommand() *ffcli.Command {
    // Export TestFlight config to YAML
}

2. Add Dependency

Add gopkg.in/yaml.v3 for YAML serialization:

go get gopkg.in/yaml.v3

3. Define YAML Schema Types

type TestFlightConfig struct {
    App     TestFlightAppConfig      `yaml:"app"`
    Groups  []TestFlightGroupConfig  `yaml:"groups"`
    Builds  []TestFlightBuildConfig  `yaml:"builds,omitempty"`
    Testers []TestFlightTesterConfig `yaml:"testers,omitempty"`
}

type TestFlightAppConfig struct {
    ID       string `yaml:"id"`
    Name     string `yaml:"name"`
    BundleID string `yaml:"bundleId"`
}

type TestFlightGroupConfig struct {
    ID                string   `yaml:"id"`
    Name              string   `yaml:"name"`
    IsInternalGroup   bool     `yaml:"isInternalGroup"`
    PublicLinkEnabled bool     `yaml:"publicLinkEnabled,omitempty"`
    PublicLinkLimit   *int     `yaml:"publicLinkLimit,omitempty"`
    FeedbackEnabled   bool     `yaml:"feedbackEnabled"`
    Builds            []string `yaml:"builds,omitempty"`
}

type TestFlightBuildConfig struct {
    ID              string   `yaml:"id"`
    Version         string   `yaml:"version"`
    UploadedDate    string   `yaml:"uploadedDate"`
    ProcessingState string   `yaml:"processingState"`
    Groups          []string `yaml:"groups,omitempty"`
}

type TestFlightTesterConfig struct {
    ID     string   `yaml:"id"`
    Email  string   `yaml:"email,omitempty"`
    Name   string   `yaml:"name,omitempty"`
    State  string   `yaml:"state"`
    Groups []string `yaml:"groups,omitempty"`
}

4. Implementation Flow

func pullTestFlightConfig(ctx context.Context, client *asc.Client, appID string) (*TestFlightConfig, error) {
    // 1. Fetch app info
    app, err := client.GetApp(ctx, appID)
    
    // 2. Fetch beta groups (with pagination)
    groups, err := client.GetBetaGroups(ctx, appID, asc.WithBetaGroupsLimit(200))
    // Paginate all
    
    // 3. Fetch builds (optional, can be large)
    builds, err := client.GetBuilds(ctx, appID, asc.WithBuildsLimit(50))
    
    // 4. Fetch testers (optional, can be very large - 1294 for FoundationLab!)
    // Consider making this opt-in with --include-testers flag
    
    // 5. Assemble config
    return &TestFlightConfig{...}, nil
}

5. CLI Flags

  • --app (required)
  • --output (required, file path like ./testflight.yaml)
  • --include-builds (optional, include builds - can be large)
  • --include-testers (optional, include testers - can be VERY large)
  • --group (optional, filter to specific group)
  • --pretty (for JSON summary output)

6. Output Behavior

  1. Write YAML to specified file
  2. Print JSON summary to stdout:
{"file":"./testflight.yaml","app":"FoundationLab","groups":2,"builds":50,"testers":0}

7. Testing

Unit tests:

  • YAML serialization tests
  • Config assembly tests

Live API test:

# Basic pull (groups only)
./asc testflight sync pull --app 6747745091 --output ./testflight.yaml

# With builds
./asc testflight sync pull --app 6747745091 --output ./tf.yaml --include-builds

# Full export (warning: testers can be huge!)
./asc testflight sync pull --app 6747745091 --output ./tf-full.yaml --include-builds --include-testers

8. File Writing Security

Use secure file writing (follow patterns from sandbox.go):

// Validate output path
if !filepath.IsAbs(outputPath) {
    outputPath = filepath.Join(cwd, outputPath)
}
outputPath = filepath.Clean(outputPath)

// Write atomically
f, err := os.CreateTemp(filepath.Dir(outputPath), ".testflight-*.yaml")
// Write to temp, then rename

9. Sample Output YAML

app:
  id: "6747745091"
  name: "FoundationLab"
  bundleId: "com.rudrankriyam.foundationlab"

groups:
  - id: "3ce80ef2-925e-4a36-b6d1-5475868714eb"
    name: "Alpha"
    isInternalGroup: true
    feedbackEnabled: true
  - id: "e601afa2-e59f-472f-9280-e35ab2f9bbe9"
    name: "Beta"
    isInternalGroup: false
    publicLinkEnabled: true
    feedbackEnabled: true

10. Code Standards

  • Sanitize file paths (prevent directory traversal)
  • Use atomic file writes
  • Consider memory usage with large tester lists
  • Add progress indicators for long operations (optional)
  • Run make format && make lint && make test before committing
<!-- gh-comment-id:3792271254 --> @rudrankriyam commented on GitHub (Jan 23, 2026): @cursor Please implement this issue. ## Implementation Guide This is an advanced feature that exports TestFlight config to YAML. It requires aggregating multiple API calls. ### 1. Create `cmd/testflight_sync.go` ```go // TestFlightSyncCommand returns the testflight sync command group. func TestFlightSyncCommand() *ffcli.Command { // Subcommand: pull (push can be added later) } func TestFlightSyncPullCommand() *ffcli.Command { // Export TestFlight config to YAML } ``` ### 2. Add Dependency Add `gopkg.in/yaml.v3` for YAML serialization: ```bash go get gopkg.in/yaml.v3 ``` ### 3. Define YAML Schema Types ```go type TestFlightConfig struct { App TestFlightAppConfig `yaml:"app"` Groups []TestFlightGroupConfig `yaml:"groups"` Builds []TestFlightBuildConfig `yaml:"builds,omitempty"` Testers []TestFlightTesterConfig `yaml:"testers,omitempty"` } type TestFlightAppConfig struct { ID string `yaml:"id"` Name string `yaml:"name"` BundleID string `yaml:"bundleId"` } type TestFlightGroupConfig struct { ID string `yaml:"id"` Name string `yaml:"name"` IsInternalGroup bool `yaml:"isInternalGroup"` PublicLinkEnabled bool `yaml:"publicLinkEnabled,omitempty"` PublicLinkLimit *int `yaml:"publicLinkLimit,omitempty"` FeedbackEnabled bool `yaml:"feedbackEnabled"` Builds []string `yaml:"builds,omitempty"` } type TestFlightBuildConfig struct { ID string `yaml:"id"` Version string `yaml:"version"` UploadedDate string `yaml:"uploadedDate"` ProcessingState string `yaml:"processingState"` Groups []string `yaml:"groups,omitempty"` } type TestFlightTesterConfig struct { ID string `yaml:"id"` Email string `yaml:"email,omitempty"` Name string `yaml:"name,omitempty"` State string `yaml:"state"` Groups []string `yaml:"groups,omitempty"` } ``` ### 4. Implementation Flow ```go func pullTestFlightConfig(ctx context.Context, client *asc.Client, appID string) (*TestFlightConfig, error) { // 1. Fetch app info app, err := client.GetApp(ctx, appID) // 2. Fetch beta groups (with pagination) groups, err := client.GetBetaGroups(ctx, appID, asc.WithBetaGroupsLimit(200)) // Paginate all // 3. Fetch builds (optional, can be large) builds, err := client.GetBuilds(ctx, appID, asc.WithBuildsLimit(50)) // 4. Fetch testers (optional, can be very large - 1294 for FoundationLab!) // Consider making this opt-in with --include-testers flag // 5. Assemble config return &TestFlightConfig{...}, nil } ``` ### 5. CLI Flags - `--app` (required) - `--output` (required, file path like `./testflight.yaml`) - `--include-builds` (optional, include builds - can be large) - `--include-testers` (optional, include testers - can be VERY large) - `--group` (optional, filter to specific group) - `--pretty` (for JSON summary output) ### 6. Output Behavior 1. Write YAML to specified file 2. Print JSON summary to stdout: ```json {"file":"./testflight.yaml","app":"FoundationLab","groups":2,"builds":50,"testers":0} ``` ### 7. Testing **Unit tests**: - YAML serialization tests - Config assembly tests **Live API test**: ```bash # Basic pull (groups only) ./asc testflight sync pull --app 6747745091 --output ./testflight.yaml # With builds ./asc testflight sync pull --app 6747745091 --output ./tf.yaml --include-builds # Full export (warning: testers can be huge!) ./asc testflight sync pull --app 6747745091 --output ./tf-full.yaml --include-builds --include-testers ``` ### 8. File Writing Security Use secure file writing (follow patterns from sandbox.go): ```go // Validate output path if !filepath.IsAbs(outputPath) { outputPath = filepath.Join(cwd, outputPath) } outputPath = filepath.Clean(outputPath) // Write atomically f, err := os.CreateTemp(filepath.Dir(outputPath), ".testflight-*.yaml") // Write to temp, then rename ``` ### 9. Sample Output YAML ```yaml app: id: "6747745091" name: "FoundationLab" bundleId: "com.rudrankriyam.foundationlab" groups: - id: "3ce80ef2-925e-4a36-b6d1-5475868714eb" name: "Alpha" isInternalGroup: true feedbackEnabled: true - id: "e601afa2-e59f-472f-9280-e35ab2f9bbe9" name: "Beta" isInternalGroup: false publicLinkEnabled: true feedbackEnabled: true ``` ### 10. Code Standards - Sanitize file paths (prevent directory traversal) - Use atomic file writes - Consider memory usage with large tester lists - Add progress indicators for long operations (optional) - Run `make format && make lint && make test` before committing
Author
Owner

@cursor[bot] commented on GitHub (Jan 23, 2026):

Summary:

  • Added the asc testflight sync pull command group with YAML export, filtering, atomic file write, and JSON summary output.
  • Implemented TestFlight config aggregation (app/groups/builds/testers) plus YAML serialization helpers and path validation.
  • Added ASC client endpoints for app detail and beta group builds/testers with corresponding tests, plus new CLI/unit tests.

Tests:

  • make format
  • make lint
  • make test

View PR
Open in Cursor Open in Web

<!-- gh-comment-id:3792271679 --> @cursor[bot] commented on GitHub (Jan 23, 2026): Summary: - Added the `asc testflight sync pull` command group with YAML export, filtering, atomic file write, and JSON summary output. - Implemented TestFlight config aggregation (app/groups/builds/testers) plus YAML serialization helpers and path validation. - Added ASC client endpoints for app detail and beta group builds/testers with corresponding tests, plus new CLI/unit tests. Tests: - `make format` - `make lint` - `make test` <a href="https://github.com/rudrankriyam/App-Store-Connect-CLI/pull/63"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/view-pr-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/view-pr-light.svg"><img alt="View PR" src="https://cursor.com/view-pr-light.svg"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-4f225fd7-e530-43e1-9913-dc6abe55690a"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-cursor-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-cursor-light.svg"><img alt="Open in Cursor" src="https://cursor.com/open-in-cursor.svg"></picture></a>&nbsp;<a href="https://cursor.com/agents?id=bc-4f225fd7-e530-43e1-9913-dc6abe55690a"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-web-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-web-light.svg"><img alt="Open in Web" src="https://cursor.com/open-in-web.svg"></picture></a>
Author
Owner

@rudrankriyam commented on GitHub (Jan 23, 2026):

Implemented in PR #63 - TestFlight sync pull command now exports app, groups, builds, and testers to YAML.

<!-- gh-comment-id:3792499615 --> @rudrankriyam commented on GitHub (Jan 23, 2026): Implemented in PR #63 - TestFlight sync pull command now exports app, groups, builds, and testers to YAML.
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#13
No description provided.