asc: App Store Connect client with auth apple, ios upload and ios submit - #20
Merged
Merged
Conversation
ES256 JWT auth from the .p8 key (15 minute tokens, cached and refreshed before expiry), generic JSON:API documents with pagination over links.next, typed decoding of the errors[] array, and retries on 429 for every method and on 5xx for idempotent ones only. Typed helpers cover what upload and submit need: apps by bundle ID, builds (list/filter, processing state, export compliance, beta group linkage), the buildUploads/buildUploadFiles chunked delivery with state polling, beta groups, beta build localizations, beta app review submissions, App Store versions and review submissions. Everything runs from the developer's machine; the runner is not involved.
builder auth apple saves the issuer ID, key ID and .p8 key as one secret through the same keyring/file storage the CI tokens use, after checking the key against the API. Flags left out are prompted for on a terminal; without one the command asks for the flags or the ASC_ISSUER_ID, ASC_KEY_ID and ASC_PRIVATE_KEY/ASC_KEY_PATH variables, which always take precedence so CI jobs and agents need no keychain. auth status and auth logout apple cover the new login.
builder ios upload reads the bundle ID, version, build number and ITSAppUsesNonExemptEncryption from the newest IPA in dist/ (or --ipa), resolves the app and runs the buildUploads flow: create the delivery, reserve the file, PUT the chunks to the presigned URLs with their request headers, commit. With --wait it polls the delivery until COMPLETE, then the build until VALID, surfacing App Store Connect's error details on failure, and answers the export compliance question when the plist declares no non-exempt encryption or --no-encryption is given. --json prints the result for agents. Info.plist reading moves from internal/dev into internal/ipa so both the dev session and the upload share it.
builder ios submit --testflight picks the newest VALID build (or --build-number), sets the What to Test notes in the app's primary locale, submits the build for beta review when a chosen group is external and adds it to the named groups; without --group it reports the build and lists the groups. --wait follows the beta review decision. builder ios submit --app-store finds or creates the App Store version for the marketing version, attaches the build, sets the release type, reuses an open review submission or creates one, adds the version and submits it. App Store Connect's 409/422 state errors, nearly always incomplete metadata, are rewritten with a hint to finish it in App Store Connect or with asc-cli.
README gets a TestFlight and App Store section after Code Signing covering the API key, upload, TestFlight and App Review steps, the build number and export compliance rules, and credits asc-cli as the reference that proved the Mac-free buildUploads path. CLAUDE.md documents the asc, distribute and ipa packages and the client, credential, upload, compliance and submit-order patterns.
Every retry and poll now sleeps through Client.sleep, so the 429 test asserts the Retry-After it was handed instead of waiting a real second, and status polls grow 1.5x per round up to 4x the base interval. Remove ListApps, GetBuildUploadFile and Error.HasCode, which nothing called, and parse the private key once instead of twice on NewClient. The beta review wait moves into asc as WaitForBetaAppReview beside the other waits. Options structs over 80 bytes are passed by pointer, and the tests check their JSON type assertions, both of which golangci-lint v2.12.2 flags in CI.
finish took the result as any, so a typed nil pointer on failure was encoded as "null" on stdout in --json mode; a generic finish[T] sees the nil. The missing-credentials error now names the environment variables next to builder auth apple, and the key-rejected error is lower-case for staticcheck.
This was referenced Sep 16, 2026
1 of 4 tasks
On Linux and WSL the login is written to a 0600 file in the config dir, not a keychain, so the auth apple confirmation was wrong there. Word it like the other provider logins instead.
Drop comments that only restated the function below them (getOne, post, patch, sleep, utiFor, logf, pollInterval, resolveIPA, getASCClient), cut the package doc for asc to what is not already in CLAUDE.md, and compress the six CLAUDE.md bullets this branch added to three lines each.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A native Go client for the App Store Connect REST API, so the build → TestFlight → App Store path works from Windows, Linux and WSL with no Mac,
altoolor Transporter. All ASC calls run on the user's machine; the runner is untouched.internal/asc: ES256 JWT from the API key (kidheader,aud appstoreconnect-v1, 15-minute expiry, refreshed early), JSON:API client with typed error decoding, pagination, 429/5xx retry with injectable sleep, and typed helpers for apps, builds,buildUploads/buildUploadFiles, beta groups, beta build localizations, beta app review submissions, app store versions, review submissions.internal/ipa: reads bundle ID, versions andITSAppUsesNonExemptEncryptionfrom an IPA's Info.plist;internal/devnow uses it instead of its own extractor.builder auth apple [--issuer-id --key-id --key]: stores the key in the OS keyring like the other providers;ASC_ISSUER_ID/ASC_KEY_ID/ASC_KEY_PATHorASC_PRIVATE_KEYoverride it for CI and agents.auth statusandauth logout applecover it.builder ios upload [--ipa] [--wait] [--timeout] [--no-encryption] [--json]: newest IPA in./dist/by default, resolves the app by bundle ID, runs the chunked upload against presigned URLs streaming from disk, optionally waits for processing and clears the export-compliance flag when the plist declares no non-exempt encryption.builder ios submit --testflight [--group]... [--notes] [--build-number] [--wait]: what-to-test notes, adds the build to the named groups, creates the beta review submission for external groups.builder ios submit --app-store [--version] [--build-number] [--release manual|after-approval]: finds or creates the version, attaches the build, sets the release type, creates and submits the review submission; ASC's 409 metadata errors are surfaced with a hint.--json(JSON only on stdout, progress on stderr) and non-zero exit codes; no prompts outsideauth apple.Test plan
go build ./... && go vet ./... && go test ./...,gofmt -l .clean, golangci-lint v2.12.2 0 issuesbuilder auth applewith a real key, thenbuilder ios upload --waitof a Release IPA signed with an App Store profilebuilder ios submit --testflight --group Internaland confirm the build appears in TestFlightbuilder ios submit --app-store --release manualon an app with completed metadata