feat(cli): unified cross-platform patch numbering#3738
Open
Conversation
Make `POST /v1/apps/{appId}/patches` idempotent on a user-supplied
correlation key so cross-platform builds resolve to one patch row
instead of one per platform.
Customer outcome: a developer who shipped "patch 3" sees the same
number on iOS and Android — in their analytics, in-app, and in their
release notes. The dominant CI flow (iOS bot publishes, Android bot
publishes minutes later, no shared state between them) collapses to
one patch as long as both bots pass the same `--patch-id`.
## Protocol — shorebird_code_push_protocol
- `CreatePatchRequest` gains optional `clientPatchId` (wire field
`client_patch_id`). `toJson` always emits the key (null when unset)
so server parsing doesn't have to special-case absence.
- `CreatePatchResponse` (new field) echoes `clientPatchId` back so
callers can verify whether an idempotent re-use occurred.
- `ReleasePatch` carries `clientPatchId` so existing GET endpoints
surface the correlation key for admin/console rendering.
## Client — shorebird_code_push_client
- `CodePushClient.createPatch` accepts and threads `clientPatchId`,
and now returns `CreatePatchResponse` instead of `Patch` so callers
can read the echoed key.
- Empty-string `clientPatchId` is normalized to null at the client
boundary — a caller that passes an unexpanded template variable or
empty flag shouldn't land on the idempotent path keyed on `''` and
inherit a stranger's patch. Done at the client rather than the
protocol so the protocol layer remains a faithful round-trip.
## CLI — shorebird_cli
- New `--patch-id=<string>` flag on `shorebird patch`. Common usage:
`--patch-id=\${{ github.sha }}` or any stable token
(`hotfix-login`, a CI run ID, etc.).
- Multi-platform invocations (`--platforms=ios,android`) auto-generate
a UUID and share it across every per-platform fan-out — single-
invocation is just cross-invocation-with-an-internal-ID.
- Append-after-promotion confirmation: when an idempotent hit lands
on a patch already promoted to stable, the new platform's artifacts
go live to that platform's stable users immediately. In interactive
mode the CLI prompts before continuing; in CI it proceeds silently
(the iOS-publishes-then-Android-completes flow is the canonical
use case and prompting would deadlock).
- Suppresses the prompt when the same run promoted the patch (e.g.
the first platform of a single `--platforms ios,android` invocation
— the user already opted in by passing both platforms together).
- Success line: `✅ Published Patch 3 (iOS, Android)` instead of one
log per platform; degenerate cases where platforms land on
different numbers each get their own line.
## Backwards compatibility
- Missing `client_patch_id` deserializes to null. Old clients that
don't set the field fall through the existing per-call create path
on the server.
- Old servers that don't recognize the field ignore it on
deserialization; the CLI flag becomes a no-op until the server
side ships, with no error surface.
This is the protocol + CLI half of unified patch numbering. The
server-side migration and idempotent createPatch live in the parent
repo and are gated behind the same `client_patch_id` correlation key.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
CI was failing on `dart format --set-exit-if-changed`. The cascade literals in the unified-success log tests exceeded 80 columns and need to be split across multiple lines.
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
Make
POST /v1/apps/{appId}/patchesidempotent on a user-supplied correlation key so cross-platform builds resolve to one patch row instead of one per platform.Customer outcome: a developer who shipped "patch 3" sees the same number on iOS and Android — in their analytics, in-app, and in their release notes. The dominant CI flow (iOS bot publishes, Android bot publishes minutes later, no shared state between them) collapses to one patch as long as both bots pass the same
--patch-id.Protocol —
shorebird_code_push_protocolCreatePatchRequestgains optionalclientPatchId(wire fieldclient_patch_id).toJsonalways emits the key (null when unset) so server parsing doesn't have to special-case absence.CreatePatchResponse(new field) echoesclientPatchIdback so callers can verify whether an idempotent re-use occurred.ReleasePatchcarriesclientPatchIdso existing GET endpoints surface the correlation key for admin/console rendering.Client —
shorebird_code_push_clientCodePushClient.createPatchaccepts and threadsclientPatchId, and now returnsCreatePatchResponseinstead ofPatchso callers can read the echoed key.clientPatchIdis normalized to null at the client boundary — a caller that passes an unexpanded template variable or empty flag shouldn't land on the idempotent path keyed on''and inherit a stranger's patch. Done at the client rather than the protocol so the protocol layer remains a faithful round-trip.CLI —
shorebird_cli--patch-id=<string>flag onshorebird patch. Common usage:--patch-id=${{ github.sha }}or any stable token (hotfix-login, a CI run ID, etc.).--platforms=ios,android) auto-generate a UUID and share it across every per-platform fan-out — single-invocation is just cross-invocation-with-an-internal-ID.--platforms ios,androidinvocation — the user already opted in by passing both platforms together).✅ Published Patch 3 (iOS, Android)instead of one log per platform; degenerate cases where platforms land on different numbers each get their own line.Backwards compatibility
client_patch_iddeserializes to null. Old clients that don't set the field fall through the existing per-call create path on the server.This is the protocol + CLI half of unified patch numbering. The server-side migration and idempotent
createPatchlive in the private parent repo and are gated behind the sameclient_patch_idcorrelation key.Test plan
dart test packages/shorebird_code_push_protocoldart test packages/shorebird_code_push_clientdart test packages/shorebird_clishorebird patch android --patch-id=test-shaagainst a server with the matching server-side change deployedshorebird patch --platforms=ios,androidend-to-end (single invocation; auto-generated UUID)shorebird patchcalls minutes apart with the same--patch-id