flightline

command module
v0.0.1-pre Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 14, 2026 License: MIT Imports: 4 Imported by: 0

README

Flightline

App Store as Code.

The first declarative tool for App Store Connect: fetch live state, edit YAML, lint, plan, apply.

CI Go Reference Go Report Card Go Version

Like Terraform for cloud infrastructure or Pulumi for Kubernetes, Flightline manages your App Store presence as declarative state. A single Go binary that fetches live App Store Connect state into YAML, runs preflight checks against Apple's rejection rules, and applies changes idempotently. The same tool reads sales, analytics, reviews, subscription state, beta feedback, and performance metrics from the terminal. The ASC web UI becomes optional.

Open source under MIT, and contributions are welcome (see CONTRIBUTING.md). No SaaS layer, no telemetry, no accounts. Just a binary that talks to Apple's API.


Table of Contents


Why Flightline

Every other modern platform has "as Code" tooling: Terraform for cloud, Pulumi for Kubernetes, Crossplane for control planes, Helm for releases. The App Store doesn't. Until now.

Apple's App Store Connect has two failure modes that cost real time.

Authoring failures. Hundreds of fields scattered across a dozen surfaces: version metadata, IAPs, IAP review screenshots, age rating, export compliance, content rights, privacy nutrition labels, review notes, contact info, demo credentials, screenshot dimensions per device, per-locale localizations, build attachment, review submission item composition. Forget any one and the release gets bounced. Every rejection is a lost release cycle.

Observation friction. Sales, downloads, conversion, reviews, subscription churn, beta crashes, performance metrics, each on a different ASC web surface, none piped, none scriptable, none LLM-readable. You spend hours per week clicking through screens to answer "how is my app doing."

Flightline addresses both. The authoring half lets you declare release state in YAML next to your app source, diff it against live ASC state, and apply changes idempotently. The observation half gives you composable terminal commands you can pipe to jq, feed to LLM prompts, or cron-schedule as snapshots. Neither half is bonus. Both are first-class.


Position

Flightline slots into the established "as Code" lineage. Same shape: declarative state, idempotent reconciliation, drift detection, version control as the source of truth.

Tool Domain "as Code" for
Terraform AWS, GCP, Azure, on-prem Infrastructure
Pulumi Cloud + Kubernetes (general-purpose languages) Infrastructure
Crossplane Multi-cloud control planes Resources
Helm Kubernetes Releases
Flightline App Store Connect App Store

If you've used any of those, the workflow rhymes: fetch to capture live state, plan to diff, apply to converge. The substrate is different (Apple's API instead of a cloud), and the failure mode being prevented is App Store rejection rather than a bad cloud rollout, but the discipline is the same.


What it does today (v0.0.1-pre)

All three layers are complete: L1 (API CLI), L2 (state-as-code), and L3 (preflight rules). The columns below split the management surface (L1 read/write CLI verbs) from the as-Code surface (L2 fetch/plan/apply) so you can see what's drivable from a state.yaml versus what's CLI-only.

Surface L1 read L1 write L2 state-as-code L3 preflight rule
Apps - - -
Versions
Builds (incl. attach)
Metadata + localizations
Screenshots ✅ ¹
IAPs (incl. review screenshot) ✅ ¹ ✅ (3 rules)
Age rating
Export compliance
Reviewer demo info -
Categories -
Pricing -
Custom product pages ✅ ¹ -
TestFlight (groups, testers, beta-review submit) ✅ (partial)
Subscription groups - ² - ² -
Review submissions (App Store Review) - ³ - ³ -
Customer reviews - ⁴ - -
Beta feedback (crash + screenshot) - - -
Diagnostic signatures - - -
Performance metrics - - -
Sales reports - - -
Finance reports - - -
Subscription reports - - -
Analytics reports - - -
Privacy nutrition labels portal-only ⁵ - - -

¹ Asset uploads (screenshots, IAP review screenshots, CPP screenshots) flow through L1 verbs by design: flightline screenshots upload, flightline iap review-screenshot upload, flightline custom-product-pages screenshots upload. Apple's multipart upload API (reserve → PUT → commit, often via a separate signed-URL host) is structurally distinct from JSON PATCH on a config field, so apply deliberately doesn't drive uploads. Config fields converge through apply, asset bytes flow through the upload verbs. The two-command flow (upload, then apply) is the intended workflow.

² Subscriptions are read-only for now (flightline subscriptions list/get/reports). Subscription writes (groups, products, prices, intro offers, promotional offers) are deferred, with no near-term plan.

³ App Store Review submission (the actual "ship this version" verb) is intentionally manual. flightline review-submissions list and flightline review-submissions items show submission state read-only; the create-and-submit flow happens in the ASC web portal. Rationale: review submission is a high-stakes, non-reversible action where double-checking in the portal is the safer default while the rest of the toolchain matures.

flightline reviews list/get/summary is read-only. Replying to reviews is not implemented.

appPrivacyDetails is absent from ASC API v4.3. flightline privacy-labels get returns a typed supported: false diagnostic explaining the gap rather than silently failing. Flightline will wire this when Apple adds the endpoint to the spec.

Architecture

Flightline is a cobra subcommand tree backed by a hand-rolled HTTP+JSON client against Apple's API. There is no codegen: Apple's OpenAPI spec triggers cascading type-name collisions in every Go generator evaluated. The spec is committed as authoritative reference and queried via jq during development.

flowchart TB
    User["You\n(or LLM / cron)"]
    YAML["state.yaml"]
    CLI["flightline CLI\nmain.go"]
    Lint["internal/lint\n11 preflight rules"]
    Plan["internal/plan\ndiff engine"]
    State["internal/state\nfetch / apply"]
    ASC["internal/asc\nhand-rolled HTTP+JSON client"]
    Auth["internal/auth\nES256 JWT (IEEE P1363)"]
    Apple[("Apple ASC API\napi.appstoreconnect.apple.com")]

    User -->|"edit"| YAML
    User -->|"flightline lint / preflight"| CLI
    User -->|"flightline plan / apply"| CLI
    User -->|"flightline sales / reviews / ..."| CLI
    YAML --> CLI
    CLI --> Lint
    CLI --> Plan
    CLI --> State
    Lint --> State
    Plan --> State
    State --> ASC
    ASC --> Auth
    Auth -->|"ES256 JWT"| Apple
    ASC -->|"HTTPS"| Apple

Layer stack:

L3: preflight rules (internal/lint/) ─── catches clerical rejection causes
L2: state-as-code  (internal/state/) ─── declare → diff → apply
L1: API CLI        (internal/asc/)   ─── every ASC surface as a terminal command

Each layer is useful standalone. You can use flightline sales and flightline reviews without ever touching a state.yaml. You can use L2 without running preflight. L3 preflight can catch issues even if you manage writes manually.


The lifecycle

Authoring (stop getting rejected)
flowchart LR
    A["1. fetch\nread live state"] --> B["2. edit\nstate.yaml"]
    B --> C["3. lint\noffline check"]
    C --> D["4. plan\ndiff vs live"]
    D --> E["5. preflight\nlive rule check"]
    E --> F["6. apply --confirm\nidempotent writes"]
    F --> G["7. submit\ntestflight beta-review submit"]
    G --> H["8. rejection\ndiagnose if bounced"]

    classDef readonly fill:#e8f5e9,stroke:#388e3c,color:#1b5e20
    classDef write fill:#fff8e1,stroke:#f9a825,color:#3e2723
    classDef commit fill:#fce4ec,stroke:#c62828,color:#b71c1c

    class A,C,D,E,H readonly
    class B write
    class F,G commit

Steps 1 to 5 are read-only and reversible. Step 6 patches ASC but does not submit for review. Step 7 (the beta-review submit) is the only action that triggers Apple Review, and it requires explicit confirmation.

Observation (stop opening the web UI)
flightline sales com.under5.passdmv --days 30
flightline finance com.under5.passdmv --month 2026-04
flightline subscriptions list com.under5.passdmv
flightline reviews list com.under5.passdmv --rating 1 --rating 2 --rating 3
flightline reviews summary com.under5.passdmv
flightline analytics request com.under5.passdmv --wait
flightline beta-feedback crash com.under5.passdmv
flightline diagnostics list com.under5.passdmv
flightline performance app com.under5.passdmv

All observation commands support --output json for piping to jq or feeding to LLM prompts.


Install

go install github.com/ul0gic/flightline@latest

Requires Go 1.26+. App Store Connect work requires a Mac, so that's where Flightline is meant to run (Apple Silicon and Intel both supported); the binary itself builds anywhere Go does.

Verify the install:

flightline --version
flightline --help

go install is the supported install method for now. To compile from a checkout, see Building from source. A Homebrew tap may follow once the project is released.


Setup

Flightline authenticates with an App Store Connect API key (a .p8 private key it signs an ES256 JWT with), not your Apple ID. One-time setup:

  1. Generate a key. App Store Connect > Users and Access > Integrations > App Store Connect API > +. Grant role App Manager (or Admin for finance reports). Click Generate, then Download API Key: the .p8 downloads only once. Note the Key ID and Issuer ID.

  2. Place the key. Move it to ~/.appstoreconnect/AuthKey_<KEY_ID>.p8 and chmod 600 it. Flightline refuses a .p8 with permissions wider than 600 and prints the exact fix.

  3. Export credentials. In ~/.zshrc (or ~/.bashrc):

    export APP_STORE_CONNECT_KEY_ID="ABCD1234EF"
    export APP_STORE_CONNECT_ISSUER_ID="12345678-90ab-cdef-1234-567890abcdef"
    export APP_STORE_CONNECT_VENDOR_NUMBER="12345678"   # sales/finance only
    
  4. Verify. Run flightline whoami; AUTHORIZED true means you are set.

For the full walkthrough (roles, troubleshooting 401/403/chmod, alternative config paths), see docs/getting-started/apple-api-key.md.

Alternative configuration paths (lower precedence than env vars): CLI flags (--key-id, --issuer-id) on every command, or a config file at ~/.config/flightline/config.yaml. See Configuration precedence for the resolution order.


Quickstart

Five commands that verify the install works and cover both pillars:

# Verify auth
flightline whoami

# List your apps
flightline apps list

# Inspect a version
flightline versions get com.under5.passdmv --version 1.0

# Diagnose a rejection (if the version is in REJECTED state)
flightline rejection com.under5.passdmv --version 1.0

# Run offline preflight against a state file
flightline lint state.yaml

Replace com.under5.passdmv with your bundle ID. For the full state-as-code walkthrough (fetch → edit → plan → apply), see docs/guides/state-as-code.md.


Commands by category

Authoring: manage release state
# Versions
flightline versions list com.under5.passdmv
flightline versions get com.under5.passdmv --version 1.1
flightline versions create com.under5.passdmv --version 1.1 --copyright "2026 ..."
flightline versions update com.under5.passdmv --version 1.1 --release-type MANUAL

# Metadata and localizations
flightline metadata set com.under5.passdmv --version 1.1 \
  --locale en-US --name "PassDMV" --subtitle "..."

# Screenshots
flightline screenshots upload com.under5.passdmv --version 1.1 \
  --locale en-US --device-set APP_IPHONE_67 ./screenshots/iphone.png

# IAPs
flightline iap list com.under5.passdmv
flightline iap create com.under5.passdmv --name "Lifetime" \
  --product-id com.under5.passdmv.lifetime --type NON_CONSUMABLE

# Age rating and compliance
flightline age-rating set com.under5.passdmv --version 1.1 --from-file rating.json
flightline export-compliance set com.under5.passdmv --version 1.1 \
  --uses-non-exempt-encryption false

# Review submissions
flightline review-submissions list com.under5.passdmv
flightline review-submissions items com.under5.passdmv --submission <id>

# Diagnose a rejection
flightline rejection com.under5.passdmv --version 1.1
Observation: read account state
# Customer reviews
flightline reviews list com.under5.passdmv --rating 1 --rating 2
flightline reviews summary com.under5.passdmv

# Sales and finance reports
flightline sales com.under5.passdmv --days 30
flightline sales com.under5.passdmv --month 2026-04 --output json
flightline finance com.under5.passdmv --month 2026-04

# Subscription reports
flightline subscriptions list com.under5.passdmv
flightline subscriptions reports com.under5.passdmv --type summary --range P30D

# Analytics (async: request, poll, download)
flightline analytics request com.under5.passdmv --access-type ONE_TIME_SNAPSHOT --wait
flightline analytics list-instances com.under5.passdmv --report-id <id>
flightline analytics download com.under5.passdmv --instance <id> --out report.csv

# TestFlight feedback and crash diagnostics
flightline beta-feedback crash com.under5.passdmv
flightline beta-feedback screenshot com.under5.passdmv
flightline diagnostics list com.under5.passdmv
flightline diagnostics get com.under5.passdmv --signature <id>

# Performance metrics
flightline performance app com.under5.passdmv
flightline performance build com.under5.passdmv --build <id>
State as Code: declare, diff, apply
# Snapshot live ASC state into a YAML file
flightline fetch com.under5.passdmv > state.yaml

# Preview what would change (no writes)
flightline plan state.yaml

# Apply changes idempotently (safe to re-run)
flightline apply state.yaml --confirm

# Resume a partially-applied run after interruption
flightline apply state.yaml --confirm --resume

See docs/reference/state-yaml.md for the full v1alpha1 schema reference and docs/guides/state-as-code.md for a step-by-step walkthrough.

Preflight: catch rejections before they happen
# Offline: validates state.yaml against JSON Schema + format rules
flightline lint state.yaml

# Live: reads ASC state, runs all 11 rules, reports pass/fail
flightline preflight com.under5.passdmv --version 1.1

# Cross-check live state against a state file
flightline preflight com.under5.passdmv --version 1.1 --state-file state.yaml

# JSON output for CI integration
flightline preflight com.under5.passdmv --version 1.1 --output json | jq '.diagnostics'

See docs/reference/preflight-rules.md for every rule with mode, severity, fix hints, and examples.


Output

Every command supports --output table (default) and --output json.

flightline apps list --output table
BUNDLE ID                  NAME         STATUS
com.under5.passdmv         PassDMV      READY_FOR_SALE
flightline apps list --output json
[
  {
    "bundleId": "com.under5.passdmv",
    "name": "PassDMV",
    "sku": "passdmv",
    "primaryLocale": "en-US"
  }
]

The JSON shape is a stable contract. Adding fields is backward-compatible; removing or renaming fields is a breaking change tracked by a major version bump. Sales and subscription commands additionally support --output tsv (passthrough from Apple's wire format).


Configuration precedence

From highest to lowest priority:

  1. CLI flags (--key-id, --issuer-id, etc.)
  2. Environment variables (APP_STORE_CONNECT_KEY_ID, APP_STORE_CONNECT_ISSUER_ID, APP_STORE_CONNECT_VENDOR_NUMBER, APP_STORE_CONNECT_KEY_PATH, FLIGHTLINE_*)
  3. Config file (~/.config/flightline/config.yaml)
  4. Defaults

Config file example (~/.config/flightline/config.yaml):

key-id: ABCD1234EF
issuer-id: 12345678-90ab-cdef-1234-567890abcdef
vendor-number: "12345678"
output: table

What it doesn't do

Not Fastlane. No pipeline DSL, no build orchestration. Flightline is the ASC config and reporting layer. xcodebuild, Xcode Cloud, and Fastlane still own compilation, signing, and binary upload.

Not a build tool. Flightline doesn't compile, archive, or upload .ipa files. You point a build at a version with builds attach; Flightline handles everything from that point forward.

Not a screenshot generator. Flightline uploads screenshots you provide via screenshots upload.

Not a SaaS. No backend, no telemetry, no accounts. The binary talks directly to Apple's API using your credentials.

Not the App Store Review submit button, by design. Flightline preps everything that goes into a submission (build attach, metadata, screenshots, IAPs, age rating, export compliance, demo creds), and flightline preflight will tell you whether the version is submission-ready, but the final "Submit for Review" click happens in the ASC web portal. Review submission is high-stakes and non-reversible; keeping the human-in-the-loop step in the portal where you can also see the full submission summary is the safer default for now. May be wired as flightline review-submissions submit in a later version once the rest of the toolchain has more real-world miles on it.

Three portal-only surfaces. Apple's public API does not expose these. Flightline tells you explicitly when you hit them rather than silently failing:

  • Resolution-center reviewer messages: the rejection text written by Apple's reviewers is not in the v4.3 API. flightline rejection reports every API-visible state field and tells you to check the portal for the actual message.
  • Privacy nutrition labels (appPrivacyDetails): entirely absent from ASC API v4.3. flightline privacy-labels get returns a typed supported: false diagnostic. Flightline will wire this when Apple adds the endpoint to the spec.
  • App Store Review submission (covered above): wired as a deliberate human-in-the-loop step, not an API gap.

Documentation

Getting started

Document What it covers
docs/getting-started/install.md Install via go install or from source
docs/getting-started/apple-api-key.md Full App Store Connect API key setup: generate, place the .p8, export env vars, verify
docs/getting-started/first-run.md The first five read-only commands

Guides

Document What it covers
docs/guides/state-as-code.md Fetch → edit → plan → apply walkthrough using passdmv
docs/guides/uploading-assets.md Uploading screenshots and IAP review screenshots via the L1 verbs

Reference

Document What it covers
docs/reference/state-yaml.md Full v1alpha1 reference: every field, type, constraint, and gotcha
docs/reference/preflight-rules.md All 11 preflight rules + submission-checklist items Apple's API doesn't expose
docs/reference/cli.md Command-group index pointing at flightline <group> --help

Concepts

Document What it covers
docs/concepts/three-layer-model.md How L1 (API CLI), L2 (state-as-code), and L3 (preflight) fit together

Building from source

Requires Go 1.26 or later.

Clone and build
git clone https://github.com/ul0gic/flightline.git
cd flightline
make build
./bin/flightline --version

The binary is at ./bin/flightline. Move it onto your PATH if you want it system-wide:

sudo mv ./bin/flightline /usr/local/bin/flightline
Or via go install
go install github.com/ul0gic/flightline@latest

The binary lands in $GOBIN (typically ~/go/bin). Make sure ~/go/bin is on your PATH.


Development

make build    # produces ./bin/flightline
make test     # go test ./... -race
make vet      # go vet ./...
make lint     # golangci-lint run
make verify   # vet + test + lint (the gate)
make fmt      # gofmt -s -w . && goimports -w .
make clean    # remove ./bin and coverage artifacts

Architecture decisions live in .project/ (gitignored, internal scaffolding). The design rationale for the hand-rolled API client instead of codegen, the JWT IEEE P1363 requirement, the async-poll state persistence model, and every other non-obvious choice are documented there.

Adding a command: query openapi.oas.json with jq for the endpoint shape, add a file under internal/asc/ for the client function, add a file under internal/cmd/ for the cobra command, add a golden fixture under internal/asc/testdata/golden/. See existing files for the pattern. It is consistent throughout.

Tests: unit tests for all command logic, HTTP fixture replay tests for the client (captured Apple responses replayed via a local server), integration tests behind //go:build integration. Run make test before any commit.


Status

v0.0.1-pre, not yet released. L1 (full API CLI, both authoring and observation pillars), L2 (state-as-code with fetch/plan/apply), and L3 (11 preflight rules) are all complete.

Planned next: GoReleaser-built release binaries, a Homebrew tap, and retiring the legacy Node CLI.

Open-sourced under MIT and maintained by ul0gic. Contributions are welcome: read CONTRIBUTING.md first. Cadence is evenings and weekends, not a funded project, so reviews may take a few days.


License

MIT. See LICENSE.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
cmd
gen-docs command
Command gen-docs regenerates the mechanical reference docs from source so they cannot drift.
Command gen-docs regenerates the mechanical reference docs from source so they cannot drift.
internal
asc
State persists to $XDG_STATE_HOME/flightline/<bundleId>/<reportClass>.json using atomic rename.
State persists to $XDG_STATE_HOME/flightline/<bundleId>/<reportClass>.json using atomic rename.
auth
Package auth handles ASC API auth.
Package auth handles ASC API auth.
cmd
The --password flag is a credential surface: it is never logged, never echoed by --verbose, and filtered out of every error string before return.
The --password flag is a credential surface: it is never logged, never echoed by --verbose, and filtered out of every error string before return.
config
Package config loads and validates Flightline state YAML files.
Package config loads and validates Flightline state YAML files.
lint
Package lint implements Flightline's preflight rule engine.
Package lint implements Flightline's preflight rule engine.
plan
Package plan computes the leaf-level change set between desired and live *config.State.
Package plan computes the leaf-level change set between desired and live *config.State.
state
Package state implements Fetch (live ASC → typed *State) and Apply (change set → ASC writes).
Package state implements Fetch (live ASC → typed *State) and Apply (change set → ASC writes).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL