play: an edit Play will not send for review (400 'set changesNotSentForReview to true', a game 1.0.1 (5) after a rejection) is validated and committed again on the same edit with changesNotSentForReview=true; dry runs validate that way and say so; prints the Publishing overview link (owner's button), records it in .storebound/ for play status; --not-sent-for-review on release / upload / rollout / listing / images and MCP / desktop; pitfall 72

This commit is contained in:
CRTkafa 2026-10-16 05:50:37 +08:00
parent dcca4e86e4
commit 220e9bb072
19 changed files with 305 additions and 41 deletions

View file

@ -2,6 +2,7 @@
// 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 --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.
// Draft app (never published): Play only accepts status "draft" on any track; the first production release is sent
@ -38,7 +39,7 @@ export async function run(ctx: Ctx): Promise<void> {
const a = ctx.args;
const aab = a._.find(x => x.endsWith('.aab'));
const fixed = a.get('version-code');
if (!aab && !fixed) throw new Error('usage: play release <app.aab> | --version-code N [--internal completed] [--production draft] [--name text] [--dry]');
if (!aab && !fixed) throw new Error('usage: play release <app.aab> | --version-code N [--internal completed] [--production draft] [--name text] [--not-sent-for-review] [--dry]');
const tracks = TRACKS.filter(t => a.has(t)).map(t => ({ track: t, status: a.get(t) === 'true' ? 'draft' : a.get(t)! }));
if (!tracks.length) tracks.push({ track: 'internal', status: 'completed' });
for (const t of tracks) if (!STATUSES.includes(t.status)) throw new Error(`--${t.track}: status must be one of ${STATUSES.join(', ')}`);
@ -80,7 +81,8 @@ export async function run(ctx: Ctx): Promise<void> {
log('play', `${t.track} <- ${code} (${t.status}, notes in ${notes.length} languages)`);
}
if (!changed && !uploaded) ctl.unchanged();
}, { dry: a.has('dry'), retries: aab ? 0 : 3 });
}, { dry: a.has('dry'), retries: aab ? 0 : 3, notSentForReview: a.has('not-sent-for-review'),
summary: `release ${a.get('name') ?? (aab ?? `versionCode ${fixed}`)}: ${tracks.map(t => `${t.track} ${t.status}`).join(', ')}` });
} catch (e) {
if (/draft app|status draft/i.test(String(e)))
throw new Error(`${(e as Error).message}\n\nThis app has never been published: the API may only create DRAFT releases. Re-run with --production draft, then send it from the Console (skill play-first-release).`);