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:
parent
dcca4e86e4
commit
220e9bb072
19 changed files with 305 additions and 41 deletions
|
|
@ -7,6 +7,7 @@ import { noteGoogle } from '../lib/quota';
|
|||
import { AmbiguousWriteError, isAmbiguous, retrySafe } from '../lib/upsert';
|
||||
import * as rt from '../lib/rt';
|
||||
import { withMachineLock } from '../lib/lock';
|
||||
import { clearPending, isReviewRefused, notSentMessage, publishingUrl, recordPending, withNotSent } from './review-pending';
|
||||
|
||||
/** A read's edit is gone: replaced by a newer edit of the app, committed elsewhere or expired. */
|
||||
export const EDIT_GONE = /Edit has been deleted|editAlreadyCommitted|already been committed|edit.*(expired|not found)|outside of this Edit/i;
|
||||
|
|
@ -73,29 +74,57 @@ export async function playClient(root: string, packageName: string) {
|
|||
/** Controls of an edit: `unchanged()` when fn found nothing to change; the edit is then deleted, not committed. */
|
||||
interface EditCtl { unchanged(): void }
|
||||
|
||||
async function editOnce<T>(fn: (id: string, ctl: EditCtl) => Promise<T>, o: { dry?: boolean; commitQuery?: string }): Promise<T> {
|
||||
/** Edit options. `notSentForReview` commits with changesNotSentForReview=true from the start (pitfall 72);
|
||||
* `summary` names the change in the local record of such a commit (play status). */
|
||||
interface EditOpts { dry?: boolean; commitQuery?: string; notSentForReview?: boolean; summary?: string }
|
||||
|
||||
async function editOnce<T>(fn: (id: string, ctl: EditCtl) => Promise<T>, o: EditOpts): Promise<T> {
|
||||
const id = await openOne();
|
||||
log('play', `edit ${id} opened`);
|
||||
let same = false;
|
||||
let committing = false;
|
||||
let notSent = !!o.notSentForReview;
|
||||
const refused = (): void => {
|
||||
notSent = true;
|
||||
log('play', 'Play refuses to send these changes for review automatically (after a rejection, other pending changes or managed publishing): validating again with changesNotSentForReview=true');
|
||||
};
|
||||
try {
|
||||
const out = await fn(id, { unchanged: () => { same = true; } });
|
||||
if (o.dry) {
|
||||
await call('POST', `${API}/edits/${id}:validate`);
|
||||
try { await call('POST', `${API}/edits/${id}:validate${withNotSent(undefined, notSent)}`); } catch (err) {
|
||||
if (notSent || !isReviewRefused(err)) throw err;
|
||||
refused();
|
||||
await call('POST', `${API}/edits/${id}:validate${withNotSent(undefined, true)}`);
|
||||
}
|
||||
log('play', 'dry run: edit validated, discarding');
|
||||
if (notSent) log('play', `dry run: the real run commits with changesNotSentForReview=true; Play then does not send it for review: Send changes for review in Publishing overview is the owner's button: ${publishingUrl()}`);
|
||||
await closeOne(id, 'dry run');
|
||||
} else if (same) {
|
||||
log('play', 'nothing to change: edit discarded, not committed');
|
||||
await closeOne(id, 'unchanged');
|
||||
} else {
|
||||
committing = true;
|
||||
try { await call('POST', `${API}/edits/${id}:commit${o.commitQuery ?? ''}`); } catch (err) {
|
||||
if (!isAmbiguous(err)) throw err;
|
||||
openEdit = null;
|
||||
throw new CommitUnknown(`the commit of edit ${id} got no clear answer (${(err as Error).message.slice(0, 200)}): it may be live. Not redone, so nothing is applied twice; check with \`storebound play status\` (or the dry run of this command) before running it again.`);
|
||||
// a commit refused with a 400 leaves the edit open and applies nothing: the same edit id is validated and
|
||||
// committed once more with the parameter (no other request is repeated)
|
||||
const commit = async (): Promise<void> => {
|
||||
try { await call('POST', `${API}/edits/${id}:commit${withNotSent(o.commitQuery, notSent)}`); } catch (err) {
|
||||
if (!isAmbiguous(err)) throw err;
|
||||
openEdit = null;
|
||||
throw new CommitUnknown(`the commit of edit ${id} got no clear answer (${(err as Error).message.slice(0, 200)}): it may be live. Not redone, so nothing is applied twice; check with \`storebound play status\` (or the dry run of this command) before running it again.`);
|
||||
}
|
||||
};
|
||||
try { await commit(); } catch (err) {
|
||||
if (notSent || !isReviewRefused(err)) throw err;
|
||||
refused();
|
||||
await call('POST', `${API}/edits/${id}:validate${withNotSent(undefined, true)}`);
|
||||
await commit();
|
||||
}
|
||||
openEdit = null;
|
||||
log('play', `edit ${id} committed`);
|
||||
log('play', `edit ${id} committed${notSent ? ' with changesNotSentForReview=true' : ''}`);
|
||||
if (notSent) {
|
||||
recordPending(root, { packageName, editId: id, committedAt: new Date().toISOString(), summary: o.summary ?? 'play edit', forced: !!o.notSentForReview });
|
||||
warn('play', notSentMessage());
|
||||
} else clearPending(root, packageName);
|
||||
}
|
||||
return out;
|
||||
} catch (err) {
|
||||
|
|
@ -132,8 +161,11 @@ export async function playClient(root: string, packageName: string) {
|
|||
* Opens an edit, runs fn, then commits (or validates and discards when dry). Any Console save or other edit commit
|
||||
* invalidates an open edit ("A change was made to the application outside of this Edit"); then the whole edit is
|
||||
* redone, up to `retries` times. Keep edits small (e.g. one per language for images) so a retry is cheap.
|
||||
* Play's 400 "Changes cannot be sent for review automatically" (pitfall 72) is answered on the same edit: validate
|
||||
* and commit again with changesNotSentForReview=true (a dry run validates that way and says so); the owner then
|
||||
* presses Send changes for review in Publishing overview, recorded in .storebound/ for `play status`.
|
||||
*/
|
||||
async edit<T>(fn: (id: string, ctl: EditCtl) => Promise<T>, o: { dry?: boolean; commitQuery?: string; retries?: number } = {}): Promise<T> {
|
||||
async edit<T>(fn: (id: string, ctl: EditCtl) => Promise<T>, o: EditOpts & { retries?: number } = {}): Promise<T> {
|
||||
return locked(o.dry ? 'dry edit' : 'edit', async () => {
|
||||
for (let left = o.retries ?? 3; ; left--) {
|
||||
try { return await editOnce(fn, o); } catch (err) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue