apple listing: a new version inherits the previous version's release type when apple.releaseType is unset; warn when the config differs
This commit is contained in:
parent
f5296cc9e9
commit
6ebb656018
5 changed files with 31 additions and 3 deletions
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
- `apple listing` creates a new version with the release type of the newest version before it (MANUAL or AFTER_APPROVAL) when `apple.releaseType` is not set; a first version stays MANUAL. The plan names where the type came from, and warns when `apple.releaseType` differs from the previous version's.
|
||||
- `play release <aab> --dry` uploads nothing: it names the versionCode it would upload and the track changes it would make (the bundle went into a discarded edit, and a long upload could outlive the edit: "This edit has expired").
|
||||
- `play build`: an absolute `--dir` / `play.build.dir` stays absolute (it was taken as a folder under the project and lost its leading slash); a `gradlew` without the exec bit (committed from Windows) is made executable with a note to commit the mode. The desktop release signs by key path only.
|
||||
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ export function listingPlanLines(p: AppleListingPlan): string[] {
|
|||
const v = p.versionPlan;
|
||||
const out = [`Version ${p.version} (${platformLabel(p.platform)}; App Store Connect has: ${v.existing.length ? v.existing.map(x => `${x.versionString} ${x.state} ${platformLabel(x.platform)}`).join(', ') : `no ${platformLabel(p.platform)} version yet`})`];
|
||||
out.push(` ${v.action === 'refuse' ? 'REFUSE' : v.action.toUpperCase()}: ${v.reason}`);
|
||||
if (v.warning) out.push(` WARN: ${v.warning}`);
|
||||
for (const ch of v.changes) out.push(` ${ch.field}: ${v.action === 'create' ? short(ch.to) : `${short(ch.from)} -> ${short(ch.to)}`}`);
|
||||
if (p.build) out.push(`Build: ${p.build.action === 'refuse' ? 'REFUSE: ' : ''}${p.build.reason}`);
|
||||
out.push(`App info ${p.appInfo.state ? `(${p.appInfo.state})` : ''}: name, subtitle, privacy policy URL per locale · ${counts(p.appInfo.locales)}`);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ export interface VersionPlan {
|
|||
/** The editable version that is kept or updated. */
|
||||
id?: string;
|
||||
reason: string;
|
||||
/** Said in the dry run and the real run (e.g. a release type that differs from the previous version's). */
|
||||
warning?: string;
|
||||
existing: VersionInfo[];
|
||||
changes: FieldDiff[];
|
||||
attributes: Record<string, string>;
|
||||
|
|
@ -103,9 +105,17 @@ export function planVersion(rows: { id: string; attributes: Record<string, unkno
|
|||
if (busy) return refuse(`version ${busy.versionString} is ${busy.state}; App Store Connect allows one version in progress (${busy.state === 'PENDING_DEVELOPER_RELEASE'
|
||||
? `release it first: storebound apple release --manual-release --version ${busy.versionString} --dry, then without --dry`
|
||||
: busy.state === 'WAITING_FOR_REVIEW' || busy.state === 'IN_REVIEW' ? 'wait for the review, or remove it from review in App Store Connect' : 'wait for its release or remove it from review'})`);
|
||||
const attributes: Record<string, string> = { platform, versionString: want, copyright: attrs.copyright, releaseType: attrs.releaseType ?? 'MANUAL' };
|
||||
// without apple.releaseType a new version keeps the release type of the newest one before it (Acme 2026-10-29: an
|
||||
// app the owner set to release after approval got a MANUAL 2.0.2 and would have sat unreleased); a first version is MANUAL
|
||||
const prev = others.slice().sort((a, b) => compareVersions(b.versionString, a.versionString))[0];
|
||||
const prevType = prev ? rows.find(r => r.id === prev.id)?.attributes.releaseType : undefined;
|
||||
const inherited = !attrs.releaseType && typeof prevType === 'string' && ['MANUAL', 'AFTER_APPROVAL'].includes(prevType) ? prevType : undefined;
|
||||
const attributes: Record<string, string> = { platform, versionString: want, copyright: attrs.copyright, releaseType: attrs.releaseType ?? inherited ?? 'MANUAL' };
|
||||
if (attributes.releaseType === 'SCHEDULED' && attrs.earliestReleaseDate) attributes.earliestReleaseDate = attrs.earliestReleaseDate;
|
||||
return { ...base, action: 'create', reason: `create version ${want} (no editable ${PLATFORM_LABEL[platform] ?? platform} version)`, changes: Object.entries(attributes).map(([field, to]) => ({ field, from: null, to })), attributes };
|
||||
const why = attrs.releaseType ? 'apple.releaseType' : inherited ? `as version ${prev!.versionString}` : prev ? `version ${prev.versionString} has ${String(prevType ?? 'no readable type')}` : 'first version';
|
||||
return { ...base, action: 'create', reason: `create version ${want} (no editable ${PLATFORM_LABEL[platform] ?? platform} version), release type ${attributes.releaseType} (${why})`,
|
||||
changes: Object.entries(attributes).map(([field, to]) => ({ field, from: null, to })), attributes,
|
||||
...(attrs.releaseType && typeof prevType === 'string' && prevType !== attrs.releaseType ? { warning: `release type ${attrs.releaseType} (apple.releaseType) differs from version ${prev!.versionString} (${prevType})` } : {}) };
|
||||
}
|
||||
|
||||
// ---- translation drafts ----
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ describe('apple listing plan', () => {
|
|||
const p = project({ ...BASE, play: undefined }, { 'store/appstore.md': APPLE_MD });
|
||||
const calls = ascRoutes([v('v-0', '1.0.0', 'READY_FOR_DISTRIBUTION')]);
|
||||
const j = jsonOut((await capture(() => appleListing(p.ctx(['--version', '1.1.0', '--dry', '--json'])))).out) as any;
|
||||
expect(j.versionPlan).toMatchObject({ action: 'create', reason: 'create version 1.1.0 (no editable iOS version)' });
|
||||
expect(j.versionPlan).toMatchObject({ action: 'create', reason: expect.stringContaining('create version 1.1.0 (no editable iOS version), release type MANUAL') });
|
||||
expect(j.versionLocales).toBeNull();
|
||||
await capture(() => appleListing(p.ctx(['--version', '1.1.0'])));
|
||||
expect(methods(calls).filter(m => m.includes('appStoreVersion'))).toEqual(['POST /v1/appStoreVersions', 'POST /v1/appStoreVersionLocalizations']);
|
||||
|
|
|
|||
16
tests/release-type-inherit.test.ts
Normal file
16
tests/release-type-inherit.test.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { expect, test } from 'bun:test';
|
||||
import { planVersion } from '../cli/lib/listing-plan';
|
||||
|
||||
const row = (id: string, versionString: string, state: string, releaseType: string) => ({ id, attributes: { versionString, appStoreState: state, appVersionState: state, platform: 'IOS', releaseType } });
|
||||
|
||||
test('a new version keeps the release type of the newest version before it unless apple.releaseType is set', () => {
|
||||
const rows = [row('v1', '2.0.0', 'READY_FOR_DISTRIBUTION', 'MANUAL'), row('v2', '2.0.1', 'READY_FOR_DISTRIBUTION', 'AFTER_APPROVAL')];
|
||||
const p = planVersion(rows, '2.0.2', { copyright: 'c' }, 'IOS');
|
||||
expect(p.action).toBe('create');
|
||||
expect(p.attributes.releaseType).toBe('AFTER_APPROVAL');
|
||||
expect(p.reason).toContain('as version 2.0.1');
|
||||
const set = planVersion(rows, '2.0.2', { copyright: 'c', releaseType: 'MANUAL' }, 'IOS');
|
||||
expect(set.attributes.releaseType).toBe('MANUAL');
|
||||
expect(set.warning).toContain('differs from version 2.0.1 (AFTER_APPROVAL)');
|
||||
expect(planVersion([], '1.0.0', { copyright: 'c' }, 'IOS').attributes.releaseType).toBe('MANUAL');
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue