SnipSnaps uses App Store Connect (asc) as the source of truth for Apple versions/builds and Changesets as the source of truth for upcoming changelog and release-note text.
Do not treat marketing/app-store-metadata/app-store-connect.json as authoritative for releases.
- Node/npm for Changesets release tooling.
- Homebrew
ascat/opt/homebrew/bin/ascfor App Store Connect. - 1Password CLI (
op) for injecting ASC credentials into the shell.
Keep ASC secrets in 1Password or a private local env file. Do not commit API key IDs, issuer IDs, private keys, private key paths, or private env filenames.
Preferred private env shape:
ASC_OP_ACCOUNT=<optional-account-domain>
ASC_OP_VAULT=<optional-vault-name>
ASC_OP_ITEM=<1password-item-id-or-name>
ASC_BYPASS_KEYCHAIN=1The script reads key_id, issuer_id, and credential from that 1Password item, writes the private key to a temporary 0600 file for asc, and deletes it on exit.
The 1Password item must have fields labeled exactly key_id, issuer_id, and credential. credential should contain the App Store Connect .p8 private key contents. Keep the actual item ID/name in a private env file or local shell history, not in git.
Run ASC-backed commands through op run:
op run --env-file <private-asc-env> -- npm run release:statusOr pass the item reference inline:
ASC_OP_ITEM=<1password-item-id-or-name> npm run release:statusSafe credential discovery for agents:
op item list --format json --long | node -e '<filter item titles/tags only; do not print fields>'
op item get <candidate-item> --format json | node -e '<print field labels/types only; do not print values>'Never print credential, key_id, issuer_id, .p8 contents, or private key paths in logs.
For each user-facing change, add a changeset before merging or releasing:
npm run changesetUse this syntax:
---
"@kyter/snipsnaps-ios": patch
---
Improved similar-photo review progress so large libraries feel more predictable.SnipSnaps commit messages can stay concise and imperative. Changeset bodies should be user-facing and App Store-ready.
Release-note text has one source of truth (Changesets) and is transformed into App Store "What's New" automatically:
- Write — each user-facing change adds a changeset (
npm run changeset) whose body is App Store-ready prose. Leading-/*/•bullets are optional; the tooling normalizes them. Prefer one change per changeset (or one bullet per line): a single changeset whose body has multiple blank-line-separated paragraphs collapses inCHANGELOG.mdinto one bullet with indented sub-paragraphs, which reads as a single item. - Snapshot —
npm run versionrunsnotes --write-default(writingdocs/next-release-notes.md) beforechangeset versionconsumes the changesets intoCHANGELOG.md. This preserves the exact notes after the changesets are gone. - Format —
formatNotes()dedupes and prepends•to every line, so App Store notes always come out as•-prefixed bullets. You never add the dot by hand. - Apply —
npm run release:apply-notes -- --platform all --confirmpushes notes to ASC (App Store "What's New" + TestFlight "What to Test"). It uses pending changesets if any, otherwise falls back todocs/next-release-notes.md. - Verify —
npm run release:notes:verify -- --platform allcompares the local notes against what is actually live in ASC for the currentMARKETING_VERSION, per platform. Exit code is non-zero on drift, so it can gate a release.
Ordering matters. docs/next-release-notes.md is a generated file that lingers in git showing the last release's notes. Always run npm run version for the new release before apply-notes, so the file is regenerated. apply-notes dry-runs by default (omit --confirm) and prints the target version + exact notes — read that before confirming, and run release:notes:verify afterward.
First release on a platform. Apple does not allow "What's New" on the first App Store version for a platform (e.g. the first macOS build). The API returns whatsNew cannot be edited at this time and the App Store shows the description instead. apply-notes now skips this case with a message instead of failing (so --platform all survives a platform launch), and verify-notes reports it as ⚠ (expected), not drift.
Use the tooling, not raw asc. Apply notes via npm run release:apply-notes so both platforms and both targets (App Store + TestFlight) stay consistent, rather than editing localizations by hand.
The release commit must be the exact commit Xcode Cloud archives. Finish every repo mutation—release notes, version files, screenshot assets, metadata drafts, and build settings—before pushing it. App Store Connect updates made after the build processes do not require a follow-up git commit.
- Review and polish pending notes:
npm run release:notes- Snapshot the App Store notes, consume changesets into
CHANGELOG.md, bumppackage.json, and sync XcodeMARKETING_VERSION:
npm run versionThis writes docs/next-release-notes.md before Changesets consumes the pending changesets, so the exact notes can still be applied after the Apple build finishes processing.
- Ask ASC for the next build number and apply it to Xcode:
op run --env-file <private-asc-env> -- npm run release:next-build -- --apply-
Finish the App Store Connect staging work that does not require a processed build: version strings, listing copy, categories, review notes, screenshots, privacy, pricing/availability, and age rating. Verify the corresponding repo drafts already match.
-
Run tests, inspect
git diff, then create and push one final release commit. Do not commit anything else to the release branch until both Xcode Cloud archives have been selected for the release. -
After the builds exist in App Store Connect, update TestFlight and App Store notes from
docs/next-release-notes.md. This command only changes App Store Connect; it does not write repo files:
op run --env-file <private-asc-env> -- npm run release:apply-notes -- --target both --confirm-
Attach the iOS and macOS builds produced from the final release commit, validate both versions, and submit both for review.
-
Tag the exact git commit that produced the Apple build:
npm run release:tag -- --confirm
git push --tagsTags use snipsnaps-ios@<marketing-version>+<build-number>.
The iOS and macOS workflows share Xcode Cloud's run-number sequence, so two
archives from the same git commit can have adjacent build numbers. Xcode Cloud
may override the checked-in CURRENT_PROJECT_VERSION; that does not break the
single-commit guarantee. Treat the marketing version and each build's
sourceCommit.commitSha as the authoritative correlation, and pass an explicit
--tag when the uploaded build number differs from the checked-in value.
When ASC credentials are active, generate correlated Apple release history:
op run --env-file <private-asc-env> -- npm run release:backfillThis writes docs/apple-release-history.md from ASC versions/builds and git commits that changed Xcode version/build settings.