137 lines
7.6 KiB
Markdown
137 lines
7.6 KiB
Markdown
# Ship step contract
|
|
|
|
How a release pipeline (a Jenkins node, a Forgejo runner, any CI job) calls storebound without a person. The
|
|
commands below keep their normal behaviour in a terminal; the contract switches on when a run passes `--json`,
|
|
`--result-file`, `--progress json`, `--env-file` or `--version-from`. The code is in one module:
|
|
`cli/lib/ship-step.ts`.
|
|
|
|
| Command | step | store |
|
|
|---|---|---|
|
|
| `storebound play build` | `build` | `play` |
|
|
| `storebound play upload` (`--track internal\|alpha\|beta\|production`) | `upload` | `play` |
|
|
| `storebound play release` (promote / several tracks in one edit) | `release` | `play` |
|
|
| `storebound apple build` | `build` | `apple` |
|
|
| `storebound apple upload` | `upload` | `apple` |
|
|
| `storebound apple submit` | `submit` | `apple` |
|
|
| `storebound apple resubmit` | `resubmit` | `apple` |
|
|
|
|
A write still needs `--yes` in a pipeline: without it the confirmation is declined and the run ends with exit 3
|
|
(`CONFIRMATION_REQUIRED`). `apple submit` without `--yes` is a dry run (exit 0, `state: "planned"`).
|
|
|
|
## Exit codes
|
|
|
|
| Code | Meaning | `error.code` |
|
|
|---|---|---|
|
|
| 0 | ok | - |
|
|
| 1 | failure (build failed, network, timeout, blockers) | `FAILED`, `TIMEOUT`, `BLOCKERS` |
|
|
| 2 | usage (unknown flag, bad value, bad `--version-from` / `--env-file`) | `USAGE`, `ENV_FILE` |
|
|
| 3 | needs the owner | `AGREEMENT_PENDING`, `SIGN_IN_REQUIRED`, `COMPLIANCE_ANSWER_MISSING`, `CONSOLE_STEP`, `CONFIRMATION_REQUIRED` |
|
|
| 4 | the store refused it (4xx answer, ITMS error, invalid build, rejected upload) | `STORE_REFUSED`, `BUILD_INVALID` |
|
|
| 7 | nothing to do: the build / release is on the store already (the result names what is there) | - |
|
|
|
|
An exit 3 carries `error.consoleUrl`: the absolute console deep link of the page where the owner acts (App Store
|
|
Connect Business > Agreements, Users and Access > Integrations, the encryption / App Privacy / age rating pages; Play
|
|
Console API access, Data safety, Content ratings, Publishing overview). The links come from
|
|
`cli/lib/console-links.ts`, with the app's console ids when the config knows them.
|
|
|
|
Note: `apple upload` and `apple testflight wait` keep their old exit 7 for a processing timeout when the contract is
|
|
off; under the contract a timeout is exit 1 `TIMEOUT` and an invalid build exit 4 `BUILD_INVALID`.
|
|
|
|
## `--json`: one final line
|
|
|
|
stdout carries exactly one line, at the end, also on failure:
|
|
|
|
```json
|
|
{"ok":true,"step":"upload","store":"play","track":"production","version":"1.2.0","build":"42","state":"completed","url":"https://play.google.com/console/u/0/developers/<dev>/app/<app>/tracks/production","warnings":[]}
|
|
```
|
|
|
|
| Key | |
|
|
|---|---|
|
|
| `ok` | true for exit 0 and 7 |
|
|
| `step`, `store` | see the table above |
|
|
| `track` | Play track(s) (`internal`, `production`, `internal,production`); null on Apple |
|
|
| `version`, `build` | marketing version and build number / versionCode, as strings |
|
|
| `state` | `built`, `planned` (dry run), Play release status (`completed`, `draft`, `inProgress`), App Store `processingState` (`VALID`, `PROCESSING`) or version state (`WAITING_FOR_REVIEW`), `failed` |
|
|
| `url` | the console page to look at |
|
|
| `warnings[]` | e.g. a versionCode that is on Play but on another track |
|
|
| `error` | `{code, message, consoleUrl?}` when not ok |
|
|
| `artifact` | build steps: the AAB / IPA path |
|
|
|
|
The command's own `--json` fields follow in the same object when they do not clash (`versionCode`, `sha256`,
|
|
`tracks`, `aab`, `processingState` ...), so readers of the old output keep working. Every log line goes to stderr.
|
|
Under `storebound serve` and flows (`STOREBOUND_MACHINE=1`) a bare `--json` keeps the old result line.
|
|
|
|
## `--result-file <path>`
|
|
|
|
The same JSON, written atomically (a temp file in the same folder, then a rename), on success and on failure. Also
|
|
written for an unknown flag (exit 2).
|
|
|
|
## `--progress json`
|
|
|
|
One JSON line on stderr per phase: `{"phase":"upload","pct":40,"message":"4/10 parts"}`. Phases: `start`,
|
|
`prebuild`, `build`, `sign`, `verify`, `upload` (with `pct`), `commit`, `processing`, `submit`, `done`. Other stderr
|
|
lines are plain logs: read the lines that start with `{`.
|
|
|
|
## `--env-file <path>`
|
|
|
|
`KEY=VALUE` lines (`#` comments, `export ` prefix, quotes; `\n` inside double quotes) loaded into the environment
|
|
before the credentials resolve; the file wins over the environment. On macOS / Linux the file must not be readable by
|
|
group or others (mode 600 or stricter), else exit 2. On Windows it is read as is. Every value of the file, and every
|
|
environment variable whose name says secret (`*_KEY`, `*PASSWORD*`, `*TOKEN*`, `ASC_P8`, `PLAY_SERVICE_ACCOUNT` ...),
|
|
is replaced with `[redacted]` in stdout, stderr and the result file (multi-line keys line by line).
|
|
|
|
## `--version-from <version.json>`
|
|
|
|
Reads the version and build for the step. Accepted shapes:
|
|
|
|
```json
|
|
{"version": "1.2.0", "build": 42}
|
|
{"versionName": "1.2.0", "versionCode": 42}
|
|
{"marketing": "1.2.0", "build": "42"}
|
|
```
|
|
|
|
(`marketingVersion` and `buildNumber` work too.) What it sets: `play build` `--version-name` + `--version-code`,
|
|
`play upload` `--version-code` (picks the AAB with that versionCode), `play release` `--version-code` (without an
|
|
.aab), `apple build` `--version` + `--build-number`, `apple upload` `--build`, `apple submit` / `resubmit` `--version`
|
|
+ `--build`. A flag given by hand must agree with the file (else exit 2). An Android build must be an integer.
|
|
|
|
## Idempotency
|
|
|
|
- `play upload`: a versionCode already in Play's bundle list is never uploaded again: exit 7 with the track and
|
|
status it has (a warning when it is not on the asked track: `play release --version-code N --<track> <status>`).
|
|
- `apple upload`: a build number App Store Connect already has (a build, or a finished build upload) is exit 7 with
|
|
its `processingState`; nothing is sent.
|
|
- `play release`: every track already holds the same release: exit 7, nothing committed.
|
|
- `apple submit`: the version is already submitted (or further) with this build, or the submission is in review:
|
|
exit 7.
|
|
|
|
## `storebound status`
|
|
|
|
```
|
|
storebound status --app <name|store id|folder> --json
|
|
```
|
|
|
|
Read-only, before any step: per platform the release reaching users (`live`) and the one above it (`next`), each with
|
|
`version`, `build`, `track`, `state`, `phase`, `reviewState` and `rolloutPct`. `--app` takes an app folder path, a
|
|
workspace app name or a store id (bundle id, package name, App Store app id). One JSON line:
|
|
|
|
```json
|
|
{"apps":[{"app":"notes","folder":"/srv/notes","platforms":[{"platform":"android","store":"play","live":{"version":"1.2.0","build":"42","track":"production","state":"inProgress","phase":"rollout","reviewState":null,"rolloutPct":20},"next":null}],"errors":[],"warnings":[]}],"at":"2026-10-23T10:00:00.000Z"}
|
|
```
|
|
|
|
Exit 1 when a store could not be read (`platforms[].error`).
|
|
|
|
## A pipeline stage
|
|
|
|
```sh
|
|
storebound play build --version-from version.json --env-file /run/secrets/ship.env --json --result-file out/build.json --progress json --yes
|
|
storebound play upload --track production --version-from version.json --env-file /run/secrets/ship.env --json --result-file out/upload.json --yes
|
|
case $? in 0|7) ;; 3) echo "owner: $(jq -r .error.consoleUrl out/upload.json)"; exit 1 ;; *) exit 1 ;; esac
|
|
```
|
|
|
|
## `--pre-check` and `--evidence` (upload steps)
|
|
|
|
`apple upload` and `play upload` take `--pre-check "<command with {file}>"` (config `upload.preCheck`): the command runs
|
|
(sh -c, project root) before any byte is sent. A nonzero exit is exit 1 with error code `PRE_CHECK_FAILED`. `--evidence
|
|
<path.json>` (config `upload.evidence`) writes `{file, sha256, size, command, exitCode, stdoutTail, timestamp,
|
|
storeboundVersion}`, also on failure; the same object is the result's `preCheck` key.
|