DEV Community

mufeng
mufeng

Posted on

I Turned App Store Connect Releases Into an Auditable Pipeline

What a real bilingual iOS release taught me about asc CLI, agent skills, stale metadata, screenshot automation, and API boundaries

An editorial illustration of a command-line release pipeline, localized metadata, screenshot batches, and a review checklist

The most annoying part of shipping an iOS app is rarely the first App Store submission.

The real drag begins with the second release: updating two sets of localized metadata, regenerating screenshots, attaching the right build, checking submission readiness, and then verifying that App Store Connect actually received what you intended to publish.

I recently moved that work for a bilingual iOS app from a browser-driven routine into a versioned release pipeline built around asc, a community-maintained App Store Connect CLI, plus a focused set of agent skills.

The result was useful, but not for the reason the “one-click release” pitch suggests.

The biggest gain was not fewer clicks. It was being able to answer three questions for every release:

  1. What did I intend to change?
  2. What did the tool actually change?
  3. What ended up live in App Store Connect?

That distinction became painfully concrete when a dry run revealed that I was about to overwrite five live fields I had never intended to touch.

Start by separating repetitive work from high-risk judgment

“App Store submission is tedious” bundles together two very different classes of work.

The first class is infrequent or judgment-heavy: agreements, tax and banking setup, initial category decisions, and App Privacy declarations. Some of this happens once. Some of it carries enough compliance risk that human review should remain explicit.

The second class happens on almost every release:

  • localized names, subtitles, descriptions, keywords, and release notes;
  • screenshots across languages and device sizes;
  • build upload and version association;
  • readiness checks and review-state monitoring;
  • post-write verification.

That second class is where automation pays for itself.

The four boundaries of an App Store release automation pipeline

The important part of this model is the boundary between responsibilities:

  • Git stores the intended release state.
  • The CLI executes operations against App Store Connect.
  • Agent skills organize the sequence, evidence, and stop conditions.
  • Apple’s APIs define what can actually be automated.
  • A developer remains responsible for high-risk decisions.

Installing more skills does not expand Apple’s public API. It only improves how reliably an agent uses the capabilities that already exist.

What asc and agent skills each contribute

asc is an unofficial, community-maintained Go CLI for App Store Connect. It exposes structured output, explicit flags, useful exit codes, and commands for metadata, screenshots, builds, TestFlight, submission, and related release work.

I verified the workflow described here against asc 5.4.0.

Agent skills sit one level above the CLI. They are not another upload mechanism. They are operational playbooks that tell an agent how to resolve IDs, choose the right command family, review a plan, diagnose a failure, and stop when the evidence is insufficient.

The simplest way to describe the relationship is:

The CLI provides actions. A skill constrains their order, evidence, and stopping conditions.

That is much more valuable than pretending the entire release can safely become a single opaque command.

Use a plan-review-apply workflow for metadata

An earlier version of my process used this sequence:

pull -> edit -> validate -> push --dry-run -> push
Enter fullscreen mode Exit fullscreen mode

In asc 5.4.0, the clearer path is plan / approve / apply, with a review artifact stored alongside the release work.

# Pull remote state before editing local files.
asc metadata pull \
  --app "$APP_ID" \
  --version "$VERSION" \
  --platform IOS \
  --dir ./metadata \
  --force

# Validate the local metadata structure and field limits.
asc metadata validate \
  --dir ./metadata \
  --output table

# Produce an artifact that can be reviewed before mutation.
asc metadata plan \
  --app "$APP_ID" \
  --version "$VERSION" \
  --platform IOS \
  --dir ./metadata \
  --review-dir .asc/metadata/review \
  --output table

# Approve the reviewed plan.
asc metadata approve \
  --review-dir .asc/metadata/review \
  --all

# Apply only the reviewed change set.
asc metadata apply \
  --app "$APP_ID" \
  --version "$VERSION" \
  --platform IOS \
  --dir ./metadata \
  --review-dir .asc/metadata/review \
  --confirm
Enter fullscreen mode Exit fullscreen mode

If you do not want to keep review artifacts yet, run apply in dry-run mode at minimum:

asc metadata apply \
  --app "$APP_ID" \
  --version "$VERSION" \
  --platform IOS \
  --dir ./metadata \
  --dry-run \
  --output table
Enter fullscreen mode Exit fullscreen mode

The point is not to collect more commands. The point is to separate content preparation from remote mutation.

If the plan includes a field you did not intend to modify, stop.

The dry run that prevented five accidental overwrites

During this release, I expected a dry run to show two additions: one promotional-text field for each language.

It showed seven changes.

The five unexpected updates affected the Chinese app name, two localized descriptions, two localized release-note fields, and a privacy-policy URL.

The cause was mundane and dangerous: I had edited those fields manually in App Store Connect, then forgotten that the local metadata copy was older. A direct push would have replaced the newer remote values with stale local content.

I stopped and used this recovery sequence:

  1. Pull the latest remote state with metadata pull --force.
  2. Reapply the intended edits to that fresh copy.
  3. Generate a new plan.
  4. Approve only the expected changes.

That incident produced the first rule of the pipeline:

Git is the long-term source of truth, but every release must still account for newer manual changes made in App Store Connect.

Without that rule, “metadata as code” can become a very efficient way to publish stale data.

A green metadata validator is not a release-readiness check

The next failure was quieter.

Version 2.3.0 was missing whatsNew in both languages. The metadata validator still reported zero errors and zero warnings.

That result was technically accurate but operationally incomplete. The validator checked the local fields it knew about. It did not prove that the version satisfied every App Store submission requirement.

A separate field audit revealed two important facts:

  • both localized release-note fields were empty;
  • the English keyword field was already at 99 out of 100 characters.

The first could block submission. The second meant that adding a keyword required removing something else first.

I now keep two gates:

Content gate

Check that required localized fields exist, match the intended release, and stay within their limits.

Release gate

Check the attached build, screenshots, review details, age rating, pricing, encryption, availability, privacy state, and any version-scoped products.

A successful validator proves only what that validator covers.

Screenshot automation is a rendering problem before it is an upload problem

This app needed six screens, two languages, and four device sizes: 48 marketing images for one release.

The API upload was not the hard part. The hard part was generating the same visual state deterministically.

I split the pipeline into three layers:

DEBUG screenshot harness
        -> Next.js composition and export
        -> asc screenshot replacement and verification
Enter fullscreen mode Exit fullscreen mode

The first layer reused an existing visual-regression harness. Launch arguments opened a specific screen directly, avoiding manual navigation.

Marketing fixtures received their own shot-* screen IDs instead of sharing regression fixtures. That separation mattered because the two datasets have different lifecycles:

  • regression fixtures should remain visually stable;
  • marketing content changes with each release narrative.

A real Chinese App Store screenshot generated by the release pipeline

This is an actual pipeline output, not a generated product mockup. The status bar is fixed, the screen is loaded from a dedicated fixture, and the waveform is deterministic.

Fixing an animated waveform without re-enabling animation

The screenshot harness disables UIKit animations so that visual regression captures remain stable.

The recording waveform had a problem: the code reset every bar to a flat state and relied on Core Animation to create the movement. With animations disabled, the screenshot showed a straight line.

Re-enabling animation would have made each capture land on a different frame. Instead, I calculated the transform for a fixed phase of the original waveform:

#if DEBUG
func vrFreezeDecorativeWaveform(phase: CGFloat = 0.8) {
    stopWaveformAnimation()
    waveformMode = .decorative

    let perBarPhase: CGFloat = 0.42
    for (index, bar) in waveformBars.enumerated() {
        let t = phase + CGFloat(index) * perBarPhase
        let scale = 0.2 + 0.8 * (0.5 + 0.5 * sin(t))
        bar.transform = CGAffineTransform(scaleX: 1, y: scale)
    }
}
#endif
Enter fullscreen mode Exit fullscreen mode

The result matches a plausible frame from the real animation and remains identical across captures.

Supplying real audio to exercise the real UI path

A voice note without a local audio file triggered the app’s “recording only available on the original device” fallback. That warning replaced the audio card I needed to show.

The fixture therefore generates a small AAC file at runtime. The real waveform extractor consumes that file, so the marketing screenshot exercises the real rendering path instead of faking an audio UI.

Both changes remain inside DEBUG-only code.

Treat screenshot replacement as a transaction

Older versions of the workflow appended screenshots. If I uploaded 48 new images without deleting the old set, App Store Connect could end up with 96.

asc 5.4.0 now supports --replace, but replacement is still destructive and should be previewed first.

# Preview deletions and uploads.
asc screenshots upload \
  --app "$APP_ID" \
  --version "$VERSION" \
  --path ./screenshots \
  --device-type IPHONE_67 \
  --replace \
  --dry-run \
  --output table

# Replace only after reviewing the plan.
asc screenshots upload \
  --app "$APP_ID" \
  --version "$VERSION" \
  --path ./screenshots \
  --device-type IPHONE_67 \
  --replace \
  --confirm
Enter fullscreen mode Exit fullscreen mode

An upload response is not enough evidence. My acceptance criteria are:

  • the dimensions encoded in each filename match the PNG dimensions;
  • Apple’s sourceFileChecksum matches the local MD5;
  • every assetDeliveryState.state becomes COMPLETE;
  • locale, device type, and ordering match the release plan.

The 61 MB upload took about eleven minutes. That was fine. An unverified upload would not have been.

Apple also supports scaling down from the highest required resolution when the UI is identical across device sizes. Maintaining four custom sizes should therefore be a design decision, not a ritual.

App Privacy is an automation boundary, not an inconvenience to bypass

App Privacy declarations describe data collection and use. They carry a different risk profile from uploading a screenshot or editing release notes.

asc has offered a web privacy path, but web-session automation is not the same as a stable public App Store Connect API. A successful request against a private web workflow is not sufficient evidence that the declaration has been published correctly.

My policy is:

  • tools may read the current state and generate a proposed plan;
  • private web-session automation is not treated as proof of publication;
  • the final state is confirmed in App Store Connect;
  • someone who understands the app, backend, SDKs, and third-party collection behavior owns the answers.

A good agent workflow must know when to stop.

Do not install every skill just because it exists

The available skill pack covers far more than a typical bilingual iOS app needs.

For my workflow, the useful group is relatively small:

  • CLI usage and ID resolution;
  • metadata synchronization and localization;
  • release-note and ASO checks;
  • screenshot pipelines;
  • build, TestFlight, and release orchestration;
  • submission health and crash triage.

Skills for Apple Ads, RevenueCat, macOS notarization, or ad hoc distribution add no value when the project does not use those systems. They only increase the context an agent must search before choosing an action.

The installation method also matters. A namespaced plugin is easier to uninstall and keeps the global skill directory clean. A commit-pinned installation is easier to audit and less likely to change unexpectedly.

That is a supply-chain tradeoff, not a question with one universally correct answer.

The release workflow I will keep

My final process deliberately avoids the fantasy of a single command:

  1. Run auth doctor and verify credentials.
  2. Resolve the app, version, build, and localization IDs.
  3. Pull remote metadata and inspect the Git diff.
  4. Validate fields and generate a review plan.
  5. Generate real screenshots and verify dimensions and order.
  6. Dry-run metadata and screenshot changes separately.
  7. Apply only reviewed changes.
  8. Pull metadata back and verify screenshot checksums.
  9. Run release-readiness checks.
  10. Keep explicit human confirmation for privacy and other high-risk boundaries.

This has more gates than a browser macro. It also makes failures visible before they become production state.

The real value of release automation

If every release requires localized metadata and multiple screenshot sets, asc plus a focused skill set is worth the investment.

It converts repeated work into files, commands, diffs, and review artifacts. It gives an agent enough structure to operate against evidence instead of guessing. It also makes rollback and post-release investigation possible.

But it is not “one-click App Store submission.” Metadata, screenshots, builds, review state, and privacy declarations remain separate resources with different risks and different API coverage.

The reliable system is not the one with the fewest steps.

It is the one that can prove what it planned, what it changed, and what Apple ultimately stored.

References

Top comments (0)