apple: release type (apple.releaseType, apple release --after-approval|--manual), device-family preflight, TestFlight invite needs an installable build

This commit is contained in:
CRTkafa 2026-10-27 17:07:37 +07:00
parent 4763862178
commit 85cf494c0a
24 changed files with 899 additions and 18 deletions

View file

@ -18,6 +18,7 @@ import { type AppleSigning, validateSigning } from './signing';
import { type AppleBuildConfig, validateAppleBuild } from './xcode';
import { type PlayBuildConfig, validatePlayBuild } from './gradle';
import { type OriginalityConfig, validateOriginality } from './originality';
import { validateReleaseType } from './release-type';
/** consumable / non_consumable: both stores. non_renewing: Apple NON_RENEWING_SUBSCRIPTION (access for a fixed time,
* you track the expiry); Google Play has no such type, so it becomes a one-time product your app consumes when the
@ -678,6 +679,14 @@ export interface ShipConfig {
screenshotTypes?: string[];
/** The app runs on iPad: renders, checks and uploads 13" iPad screenshots (2064 x 2752) and iPad app previews. */
ipad?: boolean;
/** How an approved version goes live: "afterApproval" (released as soon as App Review approves it), "manual" (waits in
* Pending Developer Release for `apple release --manual-release`) or "scheduled" (not before earliestReleaseDate).
* Applied by `apple listing` when it creates a version and by `apple submit` before submitting (old -> new in the dry
* run); `apple release --after-approval | --manual` changes it on an editable version. Unset: new versions are MANUAL
* and existing ones are left as they are. */
releaseType?: 'afterApproval' | 'manual' | 'scheduled';
/** releaseType "scheduled": the earliest release, ISO 8601 date and time (2027-03-01T09:00:00Z). */
earliestReleaseDate?: string;
/**
* Also render, check and upload a 6.3" iPhone set (Dynamic Island, medium display, 1206 x 2622 -> APP_IPHONE_61).
* Off by default: App Store Connect lists a medium-display screenshot as required for iPhone but fills it by
@ -1197,6 +1206,7 @@ export function validateConfig(c: unknown): string[] {
if (an !== undefined && (typeof an !== 'string' || !an.trim())) e.push('apple.name: the App Store name (text), or leave it out to use app.name');
else if (an !== undefined && [...an].length > APPLE_NAME_MAX) e.push(`apple.name: ${[...an].length} characters > ${APPLE_NAME_MAX} (App Store limit)`);
if (cfg.apple.appId && !/^\d+$/.test(cfg.apple.appId)) e.push(`apple.appId: numeric Apple ID of the app (${consoleLink('asc.app-info')})`);
e.push(...validateReleaseType(cfg.apple));
const enc = cfg.apple.usesNonExemptEncryption;
if (enc !== undefined && typeof enc !== 'boolean') e.push('apple.usesNonExemptEncryption: true | false');
if (cfg.apple.screenshotTypes && !cfg.apple.screenshotTypes.every(t => /^APP_[A-Z0-9_]+$/.test(t))) e.push('apple.screenshotTypes: display types like APP_IPHONE_67');