@sprid/release
v0.1.2
Published
Build, sign and release mobile apps locally. Store copy, screenshots, binary uploads and review submissions from one app-owned configuration. Dry run by default.
Maintainers
Readme
@sprid/release
The store listing as a file in your repo. Name, subtitle, keywords, description,
screenshots and Play graphics per locale live in release.config.ts, get reviewed
like code, and are pushed to App Store Connect and Google Play with one command.
Dry run by default.
Local builds, signing, binary uploads and review submission run through ship.
Use build.framework: 'expo' for Expo generation or 'native' for an existing
native tree. Other toolchains supply argv-based build commands and artifact paths.
Listing commands and direct binary uploads need no Expo or native source tree.
Bun runs the CLI; the app can use any build system.
sprid release metadata # dry run: prints every field it would send
sprid release metadata --execute # sends it
sprid release screenshots --asc # only App Store Connect
sprid release graphics --execute # Play feature graphic + iconInstall the commands with npm install -g @sprid/cli; these local tools require Bun.
TS/JS configuration executes code, including during dry runs. Use trusted configs.
Config
Use release.config.ts at the repo root, or --config <file> for TS, JS, MJS or
plain JSON. Relative credential and asset paths resolve beside that file.
--locale en-US,sv selects exact store locale codes. Omit it to use the declared
map. Only configured stores run by default; --asc / --play selects one.
An iOS-only config can omit play, and a Play-only config can omit all Apple
fields. listingLocales alone is sufficient. ascAppId can replace appId.
sprid release metadata --config ./store/listing.json --play --locale en-USimport { defineReleaseConfig } from "@sprid/release/config";
export default defineReleaseConfig({
appId: "app.example.ios", // independent iOS bundle id
asc: { keyId: "ABC123", issuer: "00000000-…", keyPath: "./.keys/AuthKey_ABC123.p8" },
play: { packageName: "app.example", keyPath: "./.keys/play-service-account.json" },
listingLocales: { apple: { "en-US": "en", "en-GB": "en", sv: "sv" }, play: { "en-US": "en", "sv-SE": "sv" } },
storeCopy: {
en: { name: "…", subtitle: "…", keywords: "…", description: "…", promotionalText: "…" },
sv: { name: "Example", description: "Localized app description" },
},
contact: { contactEmail: "[email protected]", contactWebsite: "https://example.com", defaultLanguage: "en-US" },
playGraphics: { icon: './store/icon-512.png', featureGraphic: './store/{lang}/feature-1024x500.png' },
});The Play icon is separate from the launcher icon inside the AAB. If playGraphics.icon
is omitted, execution verifies existing icons (including default-language fallback) before changing graphics.
Credentials
Never pasted into an agent conversation. Resolved in this order:
| Store | 1 | 2 |
|---|---|---|
| App Store Connect | asc.keyPath (+ asc.keyId, asc.issuer) | $ASC_KEY_PATH, $ASC_KEY_ID, $ASC_ISSUER |
| Google Play | play.keyPath | $GOOGLE_SERVICE_ACCOUNT_KEY_PATH |
A credentialsDir holding ios/AuthKey_<id>_ASC_API.p8 and
android/google-service-account.json is also honoured, for teams that keep keys in
one mounted folder outside the repo.
Validation and execution
Dry runs validate selected local copy and assets, print the payload, and return
without credentials or store API calls. Remote permissions and editability are
checked only on --execute.
- Name 30, Apple subtitle 30, Apple keywords 100 characters. Keyword phrases may contain spaces (Apple guidance). Only the selected store's fields and locales are validated.
- Locales are explicit. No localization is added automatically. To keep the old
English mirroring behavior, set
asc.englishUkFallback: true; an expliciten-GBmap always wins. Existing configs already declaringen-GBneed no change. - Apple metadata requires editable app information/version records. The command reports fields it cannot update. Screenshots replace the selected display type and any explicitly deprecated types. Missing local replacement files fail before deletion; a later upload failure can still leave a partial replacement.
- Play listing updates preserve omitted fields and never rewrite release tracks. If Play rejects a draft app's edit, resolve its release state in your existing publishing pipeline before retrying.
Credentials compatibility
play.serviceAccountKeyPath remains an alias for play.keyPath. Conflicting paths
are refused. ASC key id/issuer environment variables retain their existing
precedence over the config; explicit key file paths take precedence over env paths.
With Sprid
Add --annotate alongside --execute to record the change in Sprid using your
existing login or SPRID_PAT. Without --annotate, releases do not contact Sprid.
Annotations are best effort. Store publishing works independently of Sprid.
Library use
import { pushAscMetadata, pushPlayMetadata, loadReleaseConfig } from "@sprid/release";Listing push functions take { config, execute } and print their plan first.
Part of Sprid
This is one of the tools the free Sprid plugin drives (/sprid:store-metadata).
It works on its own. https://sprid.studio/agents
© 2026 Väder AB. All rights reserved. Use is subject to Sprid's Terms of Service.
Build, upload and submit
sprid release ship # local plan, no changes
sprid release ship --execute --patch # build, upload, submit
sprid release ship --execute --build-only # signed local artifacts
sprid release ship --execute --android-only --no-bump
sprid release asc-upload --ipa ./app.ipa --execute
sprid release play-upload --aab ./app.aab --version 1.2.3 --execute
sprid release asc-submit --version 1.2.3 --build 8 --executeship needs configFile (version source), plus build.appDir and an explicit
build.framework. Native iOS signing also needs appId, appleTeamId,
build.xcodeName and signing credentials. Android can be configured alone.
The built-in version reader supports Expo JSON, plain JSON with version,
ios.buildNumber, android.versionCode, literal TS config, and split TS/JSON.
build.credentialsJson reads EAS-format signing files locally. Inner paths resolve
beside that file (credentialsPathBase overrides). build.envFile: { path, keys } loads an explicit
allowlist without printing values. requiredProdEnv rejects missing values and
local URLs. Gradle memory settings change only with build.gradleJvmArgs.
For another toolchain, build.commands.ios or .android replaces that platform's
native build and signing steps with { command: ['tool', 'arg'], artifact: 'path' }.
Commands run in appDir; artifact paths resolve beside the release config. The
command must produce a correctly signed IPA or AAB. Direct upload commands bypass
all build/version assumptions.
Signing keys on EAS
Expo's EAS can hold the signing keys while this package does the building and
submitting. credentials-push uploads your credentials.json (Android upload
keystore, iOS distribution certificate, an App Store profile per target) to the
EAS project; credentials-pull downloads them to ~/.sprid/eas-credentials/,
owner-only. Set build.credentialsSource: 'eas' and ship pulls before every
build, so no machine keeps its own copy. The project is eas.project
(@owner/slug) or the Expo config's owner + slug; sign in with eas login or
set EXPO_TOKEN. Both commands are dry runs without --execute, print
fingerprints and ids only, and reuse anything EAS already holds byte for byte.
sprid release credentials-push --execute # once, from your local credentials.json
sprid release credentials-pull --execute # any machine, any time--submit-only resumes an iOS build or uploads an existing AAB; asc-upload uploads an IPA. --internal targets internal testing.
If Google blocks a draft app, Sprid saves the uploaded bundle as a draft on the
requested track, finishes iOS, then exits with PlayConsoleRequiredError. Complete
the first release in Play Console. finalizePlayRelease can resume an upload edit.
Apple's first version skips the unavailable What's New field. Queued submissions
are replaced only with --replace-submission. Store copy needs --push-store-copy;
upload screenshots before submitting. Logs stay in .sprid-release/logs/.
