Updates are agent steps: --yes ships an update of a live app (Play production, App Store submit / release) without an owner gate; the first release stays the owner's (exit 3 OWNER_STEP, --first-release); apple release is a ship step
This commit is contained in:
parent
c247b7e54a
commit
ae9d26d3f6
28 changed files with 562 additions and 44 deletions
|
|
@ -1,10 +1,14 @@
|
|||
// Upload an AAB (or reuse an uploaded versionCode) and put it on one or more tracks in one edit, with the "What's new"
|
||||
// of every listing language as release notes.
|
||||
// storebound play release app-release.aab [--internal completed] [--production draft] [--name "1.0.0 (3)"] [--dry]
|
||||
// storebound play release app-release.aab [--internal completed] [--production draft] [--name "1.0.0 (3)"] [--first-release] [--dry]
|
||||
// storebound play release --version-code 3 --production draft
|
||||
// storebound play release app.aab --production completed --not-sent-for-review (commit with changesNotSentForReview=true)
|
||||
// Tracks: internal, alpha (closed), beta (open), production; status completed | draft | inProgress | halted.
|
||||
// Without a track flag: internal, completed. A production release that is not a draft asks for confirmation.
|
||||
// Without a track flag: internal, completed. A production release that is not a draft asks for confirmation (--yes in
|
||||
// scripts). An UPDATE of a published app (the production track holds a completed / inProgress / halted release) needs
|
||||
// no owner step: `play release <aab> --production completed --yes` runs in a pipeline (cli/lib/release-kind.ts). The
|
||||
// first-ever production release with --yes is exit 3 OWNER_STEP unless the owner approved it (--first-release, or
|
||||
// bypass mode).
|
||||
// Draft app (never published): Play only accepts status "draft" on any track; the first production release is sent
|
||||
// from the Console (see the play-first-release skill).
|
||||
import { readFileSync, statSync } from 'node:fs';
|
||||
|
|
@ -19,6 +23,7 @@ import { usedVersionCodes } from './next-version-code';
|
|||
import { readPlayCopy, refusePlayNotes } from './listing';
|
||||
import { shipNoop, shipPhase, shipReport } from '../lib/ship-step';
|
||||
import { consoleUrl, playTrackPage } from '../lib/console-links';
|
||||
import { enforceFirstRelease, playReleaseKind } from '../lib/release-kind';
|
||||
|
||||
const TRACKS = ['internal', 'alpha', 'beta', 'production'];
|
||||
|
||||
|
|
@ -49,8 +54,20 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
for (const t of tracks) if (!STATUSES.includes(t.status)) throw new Error(`--${t.track}: status must be one of ${STATUSES.join(', ')}`);
|
||||
const fraction = a.get('fraction');
|
||||
const live = tracks.find(t => t.track === 'production' && t.status !== 'draft');
|
||||
const gated = tracks.find(t => t.track === 'production' && (t.status === 'completed' || t.status === 'inProgress'));
|
||||
const notes = readPlayCopy(ctx).filter(c => c.whatsNew).map(c => ({ language: c.lang, text: c.whatsNew }));
|
||||
refusePlayNotes(ctx, notes, `play release notes${a.has('dry') ? ' (dry run)' : ''}`);
|
||||
// update of a published app: no owner step; the first production release: the owner's (exit 3 OWNER_STEP with --yes)
|
||||
let early: Awaited<ReturnType<typeof playClient>> | undefined;
|
||||
const client = async (): Promise<Awaited<ReturnType<typeof playClient>>> => (early ??= await playClient(root, pkg));
|
||||
let kind: ReturnType<typeof playReleaseKind> | null = null;
|
||||
if (gated) {
|
||||
const p0 = await client();
|
||||
const prod = await p0.read(async id => (await p0.call<{ releases?: PlayTrackRelease[] }>('GET', `${p0.API}/edits/${id}/tracks/production`).catch(() => ({ releases: [] }))).releases ?? []);
|
||||
kind = playReleaseKind(prod);
|
||||
if (a.has('dry')) log('play', `${kind.kind === 'update' ? 'update of a published app' : 'FIRST production release (the owner approves it once: --first-release)'}: ${kind.why}`);
|
||||
else if (!enforceFirstRelease(ctx, kind, { store: 'play', what: 'the first production release', consoleUrl: consoleUrl('play.publishing') })) return;
|
||||
}
|
||||
if (live && !a.has('dry') && !(await confirm(ctx, `Release to PRODUCTION as "${live.status}" (public once Google approves)?`))) return;
|
||||
|
||||
if (aab) {
|
||||
|
|
@ -59,7 +76,7 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
if (sdkWhy) throw new Error(`${aab}: not released, ${sdkWhy}`);
|
||||
}
|
||||
shipReport({ track: tracks.map(t => t.track).join(','), url: consoleUrl(playTrackPage(tracks.length === 1 ? tracks[0]!.track : undefined)) });
|
||||
const play = await playClient(root, pkg);
|
||||
const play = await client();
|
||||
let released = fixed ?? '';
|
||||
let nothing = false;
|
||||
try {
|
||||
|
|
@ -116,6 +133,6 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
}
|
||||
// machine-readable result for CI scripts (a Jenkins stage reads it instead of calling the Play API itself)
|
||||
if (a.has('json')) printJson({ versionCode: released ? Number(released) : null, sha256: aab ? createHash('sha256').update(readFileSync(aab)).digest('hex') : null, name: a.get('name') ?? null,
|
||||
tracks: tracks.map(t => ({ track: t.track, status: t.status, userFraction: t.status === 'inProgress' ? Number(fraction ?? 0.1) : null })), dry: a.has('dry') });
|
||||
tracks: tracks.map(t => ({ track: t.track, status: t.status, userFraction: t.status === 'inProgress' ? Number(fraction ?? 0.1) : null })), dry: a.has('dry'), ...(kind ? { releaseKind: kind.kind } : {}) });
|
||||
done('play', 'release done');
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue