Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

- Add read-only release lifecycle states with exact version-code matching.

## 0.1.0

- Initial release: `@appeeky/google-play-store-core`, `gps` CLI, `gps-mcp`
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ Details: [docs/authentication-accounts.mdx](./docs/authentication-accounts.mdx).
```bash
gps --package com.example.app deploy --file ./app.aab --track internal --confirm
gps --package com.example.app tracks promote --from internal --to beta --confirm
gps --package com.example.app tracks releases production --version-code 123 --json
gps --package com.example.app tracks rollout production --user-fraction 0.1 --confirm
gps --package com.example.app tracks halt production --confirm
```
Expand Down
27 changes: 27 additions & 0 deletions docs/cli/releases.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,36 @@ gps -p com.example.app deploy \
```bash
gps -p com.example.app tracks list
gps -p com.example.app tracks get production
gps -p com.example.app tracks releases production --version-code 123 --json
gps -p com.example.app tracks promote --from beta --to production --user-fraction 0.05 --confirm
gps -p com.example.app tracks rollout production --user-fraction 0.25 --confirm
gps -p com.example.app tracks halt production --confirm
```

## Release lifecycle

This command reads the lifecycle state without opening an edit:

```bash
gps -p com.example.app tracks releases production --version-code 123 --json
```

Match the exact version code in release gates.

`RELEASE_LIFECYCLE_STATE_APPROVED_NOT_PUBLISHED` means Google approved the release.
You must publish these changes in Play Console.
The Android Publisher API does not support that final action.
See the [release lifecycle API](https://developers.google.com/android-publisher/api-ref/rest/v3/applications.tracks.releases).
See the [managed publishing guide](https://support.google.com/googleplay/android-developer/answer/9859654).

The command also supports MCP and `gps call`:

```bash
gps call gps_list_release_summaries '{
"packageName": "com.example.app",
"track": "production",
"versionCode": 123
}'
```

Guides: [Deploy a release](/guides/deploy-release), [Staged rollout](/guides/staged-rollout).
3 changes: 2 additions & 1 deletion docs/reference/operations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Write operations are marked **write** and require `--confirm` on the CLI (or age

Most Publisher tools also accept an optional `account` argument for multi-key setups. See [Multi-account and apps](/authentication-accounts).

Total operations: **129**
Total operations: **130**

## accounts

Expand Down Expand Up @@ -53,6 +53,7 @@ Total operations: **129**
| `gps_list_tracks` | read | List release tracks |
| `gps_get_track` | read | Get a release track |
| `gps_list_track_releases` | read | List releases on a track |
| `gps_list_release_summaries` | read | List release lifecycle states on a track |
| `gps_update_track` | **write** | Update a track inside a committed edit |
| `gps_promote_release` | **write** | Promote a release between tracks |
| `gps_update_rollout` | **write** | Update staged rollout fraction |
Expand Down
31 changes: 31 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,37 @@ export function buildProgram(): Command {
printJson(result);
});

tracks
.command("releases")
.description("List release lifecycle states without opening an edit")
.argument("<track>")
.option("--version-code <code>", "Match an exact active artifact version code")
.action(async (track: string, cmdOpts: { versionCode?: string }) => {
const opts = program.opts<GlobalOpts>();
const client = await clientFrom(opts);
const packageName = pkg(opts);
const result = await client.tracks.listReleaseSummaries(packageName, track, {
versionCode: cmdOpts.versionCode,
});
if (cmdOpts.versionCode !== undefined && result.length === 0) {
throw new Error(
`No active release with version code ${cmdOpts.versionCode} exists on track ${track}`,
);
}
if (opts.json) printJson(result);
else
printTable(
result.map((release) => ({
release: release.releaseName,
track: release.track,
versionCodes: release.activeArtifacts
?.map((artifact) => artifact.versionCode)
.join(","),
lifecycleState: release.releaseLifecycleState,
})),
);
});

tracks
.command("promote")
.description("Promote a release from one track to another")
Expand Down
50 changes: 50 additions & 0 deletions packages/core/src/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,56 @@ describe("edits.withEdit", () => {
});
});

describe("tracks.listReleaseSummaries", () => {
it("reads lifecycle state and matches the exact version code", async () => {
let parent: string | undefined;
const publisher = {
applications: {
tracks: {
releases: {
list: async (params: { parent?: string }) => {
parent = params.parent;
return {
data: {
releases: [
{
releaseName: "1.2.3",
track: "production",
activeArtifacts: [{ versionCode: 123 }],
releaseLifecycleState:
"RELEASE_LIFECYCLE_STATE_APPROVED_NOT_PUBLISHED",
},
{
releaseName: "1.2.2",
track: "production",
activeArtifacts: [{ versionCode: 122 }],
releaseLifecycleState: "RELEASE_LIFECYCLE_STATE_PUBLISHED",
},
],
},
};
},
},
},
},
} as unknown as Parameters<typeof PlayStoreClient.fromPublisher>[0];

const client = PlayStoreClient.fromPublisher(publisher, { readOnly: true });
const releases = await client.tracks.listReleaseSummaries(
"com.example.app",
"production",
{ versionCode: 123 },
);

expect(parent).toBe("applications/com.example.app/tracks/production");
expect(releases).toHaveLength(1);
expect(releases[0]?.activeArtifacts?.[0]?.versionCode).toBe(123);
expect(releases[0]?.releaseLifecycleState).toBe(
"RELEASE_LIFECYCLE_STATE_APPROVED_NOT_PUBLISHED",
);
});
});

describe("config multi-account", () => {
it("registers accounts and apps and finds package owners", async () => {
const { mkdtempSync, rmSync } = await import("node:fs");
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ export { ClientRegistry, type ClientRegistryOptions } from "./registry.js";

export type { DeployOptions, DeployResult } from "./resources/deploy.js";
export type { ImageType } from "./resources/listings.js";
export type {
ReleaseSummary,
ListReleaseSummariesOptions,
} from "./resources/tracks.js";
17 changes: 17 additions & 0 deletions packages/core/src/ops-part1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,23 @@ export const GPS_OPS: GpsOp[] = [
run: async (c, a) =>
c.tracks.listReleases(asString(a.packageName, "packageName"), asString(a.track, "track")),
},
{
name: "gps_list_release_summaries",
group: "tracks",
summary: "List release lifecycle states on a track",
write: false,
shape: {
packageName: pkg,
track: z.string(),
versionCode: z.union([z.string(), z.number()]).optional(),
},
run: async (c, a) =>
c.tracks.listReleaseSummaries(
asString(a.packageName, "packageName"),
asString(a.track, "track"),
{ versionCode: a.versionCode as string | number | undefined },
),
},
{
name: "gps_update_track",
group: "tracks",
Expand Down
28 changes: 28 additions & 0 deletions packages/core/src/resources/tracks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { assertPackageName, assertTrack } from "../validation.js";

export type Track = androidpublisher_v3.Schema$Track;
export type TrackRelease = androidpublisher_v3.Schema$TrackRelease;
export type ReleaseSummary = androidpublisher_v3.Schema$ReleaseSummary;

export interface ListReleaseSummariesOptions {
versionCode?: number | string;
}

export class TracksApi {
constructor(private readonly client: PlayStoreClient) {}
Expand Down Expand Up @@ -98,6 +103,29 @@ export class TracksApi {
});
}

async listReleaseSummaries(
packageName: string,
track: string,
options: ListReleaseSummariesOptions = {},
): Promise<ReleaseSummary[]> {
assertPackageName(packageName);
assertTrack(track);
return this.client.request(async () => {
const res = await this.client.publisher.applications.tracks.releases.list({
parent: `applications/${packageName}/tracks/${track}`,
});
const releases = res.data.releases ?? [];
if (options.versionCode === undefined) return releases;

const versionCode = String(options.versionCode);
return releases.filter((release) =>
release.activeArtifacts?.some(
(artifact) => String(artifact.versionCode) === versionCode,
),
);
});
}

/** Convenience: open ephemeral edit, list tracks, discard edit */
async listCommitted(packageName: string): Promise<Track[]> {
return this.client.edits.withEphemeralEdit(packageName, async (editId) => {
Expand Down
11 changes: 9 additions & 2 deletions skills/gps-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ gps --package $PKG tracks promote --from internal --to beta --confirm
gps --package $PKG tracks promote --from beta --to production --user-fraction 0.1 --confirm
```

4. Expand or halt:
4. Read the lifecycle state for the exact version:

```bash
gps --package $PKG tracks releases production --version-code $VERSION_CODE --json
```

5. Expand or halt:

```bash
gps --package $PKG tracks list --json
Expand All @@ -35,4 +41,5 @@ gps --package $PKG tracks halt production --confirm
- Never skip `--confirm` on writes.
- Prefer internal/alpha before production.
- Use `--validate-only` when dry-running supported commands.
- MCP: `gps_deploy_app`, `gps_promote_release`, `gps_update_rollout`, `gps_halt_release`.
- Publish approved managed releases in Play Console.
- MCP: `gps_deploy_app`, `gps_promote_release`, `gps_list_release_summaries`, `gps_update_rollout`, `gps_halt_release`.
Loading