@mpgd/cli 0.67.0 → 0.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,168 @@
1
+ /**
2
+ * App Store Connect release port: the App Store version, its build, App
3
+ * Review information, release options, the age rating declaration and the
4
+ * review submission. `app-store-release-asc.ts` implements it with the
5
+ * pinned `asc` CLI; the planner and tests see only these shapes. The store
6
+ * page reads and the version localization writes come from
7
+ * `app-store-presence-port.ts`, and the in-app purchase reads from
8
+ * `app-store-iap-port.ts`.
9
+ *
10
+ * Resources (App Store Connect API, read 2026-10-31):
11
+ * - App Store versions: https://developer.apple.com/documentation/appstoreconnectapi/appstoreversion
12
+ * (`releaseType` MANUAL, AFTER_APPROVAL or SCHEDULED; `earliestReleaseDate`; `copyright`)
13
+ * - The version's build: https://developer.apple.com/documentation/appstoreconnectapi/patch-v1-appstoreversions-_id_-relationships-build
14
+ * - Builds: https://developer.apple.com/documentation/appstoreconnectapi/build
15
+ * (`processingState` PROCESSING, FAILED, INVALID or VALID; `usesNonExemptEncryption`)
16
+ * - App Review information: https://developer.apple.com/documentation/appstoreconnectapi/post-v1-appstorereviewdetails
17
+ * - Phased release: https://developer.apple.com/documentation/appstoreconnectapi/post-v1-appstoreversionphasedreleases
18
+ * (`PhasedReleaseState` INACTIVE, ACTIVE, PAUSED or COMPLETE)
19
+ * - Review submissions: https://developer.apple.com/documentation/appstoreconnectapi/post-v1-reviewsubmissions,
20
+ * their items https://developer.apple.com/documentation/appstoreconnectapi/post-v1-reviewsubmissionitems,
21
+ * and submitting one https://developer.apple.com/documentation/appstoreconnectapi/patch-v1-reviewsubmissions-_id_
22
+ * - Manual release: https://developer.apple.com/documentation/appstoreconnectapi/post-v1-appstoreversionreleaserequests
23
+ */
24
+ export type AscReleaseType = 'MANUAL' | 'AFTER_APPROVAL' | 'SCHEDULED';
25
+ export interface AscReleaseApp {
26
+ readonly bundleId: string;
27
+ /** The app's primary language, for example `en-US`. */
28
+ readonly primaryLocale?: string | undefined;
29
+ }
30
+ export interface AscReleaseVersion {
31
+ readonly id: string;
32
+ readonly versionString: string;
33
+ /** `appVersionState`, or `appStoreState` when App Store Connect sends only that. */
34
+ readonly state?: string | undefined;
35
+ /** The legacy `appStoreState`. */
36
+ readonly appStoreState?: string | undefined;
37
+ readonly releaseType?: string | undefined;
38
+ readonly earliestReleaseDate?: string | undefined;
39
+ readonly copyright?: string | undefined;
40
+ readonly createdDate?: string | undefined;
41
+ }
42
+ export interface AscReleaseBuild {
43
+ readonly id: string;
44
+ /** The build number (`CFBundleVersion`). */
45
+ readonly buildNumber: string;
46
+ readonly processingState?: string | undefined;
47
+ readonly expired?: boolean | undefined;
48
+ /** `undefined` while App Store Connect waits for the export compliance answer. */
49
+ readonly usesNonExemptEncryption?: boolean | undefined;
50
+ readonly uploadedDate?: string | undefined;
51
+ }
52
+ export interface AscReviewDetail {
53
+ readonly id: string;
54
+ readonly contactFirstName?: string | undefined;
55
+ readonly contactLastName?: string | undefined;
56
+ readonly contactEmail?: string | undefined;
57
+ readonly contactPhone?: string | undefined;
58
+ readonly demoAccountName?: string | undefined;
59
+ /** asc withholds the password; only whether one is set is known. */
60
+ readonly demoAccountPasswordSet: boolean;
61
+ readonly demoAccountRequired: boolean;
62
+ readonly notes?: string | undefined;
63
+ }
64
+ /** The fields a review detail write sends; a field left out stays as it is. */
65
+ export interface AscReviewDetailWrite {
66
+ readonly contactFirstName?: string;
67
+ readonly contactLastName?: string;
68
+ readonly contactEmail?: string;
69
+ readonly contactPhone?: string;
70
+ readonly notes?: string;
71
+ readonly demoAccountRequired?: boolean;
72
+ readonly demoAccountName?: string;
73
+ readonly demoAccountPassword?: string;
74
+ }
75
+ export interface AscPhasedRelease {
76
+ readonly id: string;
77
+ /** INACTIVE, ACTIVE, PAUSED or COMPLETE. */
78
+ readonly state: string;
79
+ readonly currentDayNumber?: number | undefined;
80
+ readonly startDate?: string | undefined;
81
+ }
82
+ export interface AscAgeRating {
83
+ readonly id: string;
84
+ /** The answered content descriptors, by attribute name. */
85
+ readonly answered: readonly string[];
86
+ }
87
+ export interface AscReviewSubmission {
88
+ readonly id: string;
89
+ /** READY_FOR_REVIEW, WAITING_FOR_REVIEW, IN_REVIEW, UNRESOLVED_ISSUES, CANCELING, COMPLETING or COMPLETE. */
90
+ readonly state: string;
91
+ readonly submittedDate?: string | undefined;
92
+ }
93
+ export interface AscReviewSubmissionItem {
94
+ readonly id: string;
95
+ /** READY_FOR_REVIEW, ACCEPTED, APPROVED, REJECTED or REMOVED. */
96
+ readonly state?: string | undefined;
97
+ readonly appStoreVersionId?: string | undefined;
98
+ readonly inAppPurchaseVersionId?: string | undefined;
99
+ }
100
+ export type AscReviewItemType = 'appStoreVersions' | 'inAppPurchaseVersions';
101
+ export interface AscReleasePort {
102
+ readApp(appId: string): Promise<AscReleaseApp>;
103
+ /** iOS App Store versions of the app, with their release options. */
104
+ listVersions(appId: string): Promise<AscReleaseVersion[]>;
105
+ /** iOS builds of one marketing version, optionally one build number. */
106
+ listBuilds(input: {
107
+ readonly appId: string;
108
+ readonly marketingVersion: string;
109
+ readonly buildNumber?: string;
110
+ }): Promise<AscReleaseBuild[]>;
111
+ /** The build attached to the version, if any. */
112
+ readAttachedBuild(versionId: string): Promise<{
113
+ readonly id: string;
114
+ readonly buildNumber?: string;
115
+ } | undefined>;
116
+ /** `undefined` when the version has no App Review information yet. */
117
+ readReviewDetail(versionId: string): Promise<AscReviewDetail | undefined>;
118
+ /** `undefined` when the version has no phased release. */
119
+ readPhasedRelease(versionId: string): Promise<AscPhasedRelease | undefined>;
120
+ /** The age rating declaration of an app information record. */
121
+ readAgeRating(appInfoId: string): Promise<AscAgeRating | undefined>;
122
+ /** iOS review submissions of the app, every state. */
123
+ listReviewSubmissions(appId: string): Promise<AscReviewSubmission[]>;
124
+ listReviewSubmissionItems(submissionId: string): Promise<AscReviewSubmissionItem[]>;
125
+ /** Returns the new version's ID. */
126
+ createVersion(input: {
127
+ readonly appId: string;
128
+ readonly versionString: string;
129
+ readonly releaseType: AscReleaseType;
130
+ readonly copyright?: string;
131
+ }): Promise<string>;
132
+ updateVersion(input: {
133
+ readonly versionId: string;
134
+ readonly versionString?: string;
135
+ readonly copyright?: string;
136
+ readonly releaseType?: AscReleaseType;
137
+ readonly earliestReleaseDate?: string;
138
+ }): Promise<void>;
139
+ attachBuild(input: {
140
+ readonly versionId: string;
141
+ readonly buildId: string;
142
+ }): Promise<void>;
143
+ createReviewDetail(input: {
144
+ readonly versionId: string;
145
+ readonly fields: AscReviewDetailWrite;
146
+ }): Promise<void>;
147
+ updateReviewDetail(input: {
148
+ readonly detailId: string;
149
+ readonly fields: AscReviewDetailWrite;
150
+ }): Promise<void>;
151
+ /** A new phased release in INACTIVE: it starts when the version is released. */
152
+ createPhasedRelease(versionId: string): Promise<void>;
153
+ deletePhasedRelease(phasedReleaseId: string): Promise<void>;
154
+ updatePhasedRelease(input: {
155
+ readonly phasedReleaseId: string;
156
+ readonly state: 'ACTIVE' | 'PAUSED' | 'COMPLETE';
157
+ }): Promise<void>;
158
+ /** Returns the new submission's ID. */
159
+ createReviewSubmission(appId: string): Promise<string>;
160
+ addReviewSubmissionItem(input: {
161
+ readonly submissionId: string;
162
+ readonly type: AscReviewItemType;
163
+ readonly id: string;
164
+ }): Promise<void>;
165
+ submitReviewSubmission(submissionId: string): Promise<void>;
166
+ /** Releases a version in PENDING_DEVELOPER_RELEASE. */
167
+ releaseVersion(versionId: string): Promise<void>;
168
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * App Store Connect release port: the App Store version, its build, App
3
+ * Review information, release options, the age rating declaration and the
4
+ * review submission. `app-store-release-asc.ts` implements it with the
5
+ * pinned `asc` CLI; the planner and tests see only these shapes. The store
6
+ * page reads and the version localization writes come from
7
+ * `app-store-presence-port.ts`, and the in-app purchase reads from
8
+ * `app-store-iap-port.ts`.
9
+ *
10
+ * Resources (App Store Connect API, read 2026-10-31):
11
+ * - App Store versions: https://developer.apple.com/documentation/appstoreconnectapi/appstoreversion
12
+ * (`releaseType` MANUAL, AFTER_APPROVAL or SCHEDULED; `earliestReleaseDate`; `copyright`)
13
+ * - The version's build: https://developer.apple.com/documentation/appstoreconnectapi/patch-v1-appstoreversions-_id_-relationships-build
14
+ * - Builds: https://developer.apple.com/documentation/appstoreconnectapi/build
15
+ * (`processingState` PROCESSING, FAILED, INVALID or VALID; `usesNonExemptEncryption`)
16
+ * - App Review information: https://developer.apple.com/documentation/appstoreconnectapi/post-v1-appstorereviewdetails
17
+ * - Phased release: https://developer.apple.com/documentation/appstoreconnectapi/post-v1-appstoreversionphasedreleases
18
+ * (`PhasedReleaseState` INACTIVE, ACTIVE, PAUSED or COMPLETE)
19
+ * - Review submissions: https://developer.apple.com/documentation/appstoreconnectapi/post-v1-reviewsubmissions,
20
+ * their items https://developer.apple.com/documentation/appstoreconnectapi/post-v1-reviewsubmissionitems,
21
+ * and submitting one https://developer.apple.com/documentation/appstoreconnectapi/patch-v1-reviewsubmissions-_id_
22
+ * - Manual release: https://developer.apple.com/documentation/appstoreconnectapi/post-v1-appstoreversionreleaserequests
23
+ */
24
+ export {};
@@ -0,0 +1,238 @@
1
+ import type { StorePresence } from '@mpgd/catalog';
2
+ import type { AscIapPort } from './app-store-iap-port.js';
3
+ import type { AscStorePresencePort } from './app-store-presence-port.js';
4
+ import type { AscPhasedRelease, AscReleasePort, AscReleaseType, AscReviewSubmissionItem } from './app-store-release-port.js';
5
+ import { type StorePresenceTextChange } from './store-presence.js';
6
+ /**
7
+ * App Store Connect release: everything between "the build is in
8
+ * TestFlight" and "the version is submitted for review", planned from App
9
+ * Store Connect and the game files, then applied step by step.
10
+ *
11
+ * The plan reads; apply re-plans, refuses when App Store Connect or the
12
+ * files moved past the reviewed plan, and writes in a fixed order: the
13
+ * version (created or renamed, then its copyright and release options), its
14
+ * build, What's New, App Review information, the phased release, the review
15
+ * submission with the version and the in-app purchases that go with it, and
16
+ * the submission itself. App Store Connect has no transaction: a failed
17
+ * apply names the steps already written, and a new plan lists what is left.
18
+ *
19
+ * A version that does not exist yet is created alone: App Store Connect
20
+ * then needs its store page (`mpgd store presence apply`) before anything
21
+ * else can be checked, so the next plan picks up from there.
22
+ */
23
+ export declare const appStoreReleasePlanCommand = "mpgd store release plan";
24
+ export type AppStoreReleaseStage = 'create-version' | 'prepare' | 'submitted' | 'approved' | 'released';
25
+ export interface AppStoreReleaseRequest {
26
+ /** The marketing version (`CFBundleShortVersionString`) to submit. */
27
+ readonly marketingVersion: string;
28
+ /** The build number; without it, the newest build of the version. */
29
+ readonly buildNumber?: string;
30
+ /** The App Store Connect build `deploy run` recorded for this build, when known. */
31
+ readonly recordedBuildId?: string;
32
+ /** The iOS app runs on iPad too: App Review needs 13-inch iPad screenshots. */
33
+ readonly universal: boolean;
34
+ readonly releaseType: AscReleaseType;
35
+ /** ISO 8601 date-time; required for, and only for, SCHEDULED. */
36
+ readonly earliestReleaseDate?: string;
37
+ readonly phasedRelease: boolean;
38
+ /** In-app purchase product IDs deliberately left out of this submission. */
39
+ readonly skipIaps: readonly string[];
40
+ /** Send the demo account password again even when App Store Connect has one. */
41
+ readonly rotateDemoPassword: boolean;
42
+ }
43
+ /** The demo account values the presence file names by environment variable. */
44
+ export interface AppStoreReleaseDemoAccount {
45
+ readonly name: string;
46
+ readonly password: string;
47
+ }
48
+ export interface AppStoreReleasePlanInput {
49
+ readonly appId: string;
50
+ readonly bundleId: string;
51
+ readonly presence: StorePresence;
52
+ readonly request: AppStoreReleaseRequest;
53
+ /** Resolved from `appStore.review.demoAccount`; never saved. */
54
+ readonly demoAccount?: AppStoreReleaseDemoAccount;
55
+ /**
56
+ * Why the demo account could not be read (an unset variable, say). The plan
57
+ * is still made, with these as blockers, so it can be reviewed without the secrets.
58
+ */
59
+ readonly demoAccountProblems?: readonly string[];
60
+ readonly now?: () => Date;
61
+ }
62
+ export interface AppStoreReleasePorts {
63
+ readonly release: AscReleasePort;
64
+ readonly presence: AscStorePresencePort;
65
+ readonly iap: AscIapPort;
66
+ }
67
+ export interface PlannedReleaseIap {
68
+ readonly productId: string;
69
+ readonly iapId: string;
70
+ readonly type: string;
71
+ readonly state: string;
72
+ /** `attach`: added to the submission; `attached`: already in it. */
73
+ readonly action: 'attach' | 'attached' | 'skip' | 'none' | 'blocked';
74
+ readonly versionId?: string;
75
+ }
76
+ export interface AppStoreReleasePlan {
77
+ readonly schemaVersion: 1;
78
+ readonly store: 'app-store';
79
+ readonly appId: string;
80
+ readonly bundleId: string;
81
+ readonly marketingVersion: string;
82
+ readonly stage: AppStoreReleaseStage;
83
+ readonly version: {
84
+ readonly action: 'create' | 'rename' | 'reuse';
85
+ readonly id?: string;
86
+ /** `rename`: the editable version's current version string. */
87
+ readonly from?: string;
88
+ readonly state?: string;
89
+ /** No version of the app has been released: App Store Connect takes no What's New. */
90
+ readonly first: boolean;
91
+ };
92
+ /** One `versions update` (copyright, release type, earliest release date), when anything differs. */
93
+ readonly versionUpdate?: {
94
+ readonly copyright?: string;
95
+ readonly releaseType?: AscReleaseType;
96
+ readonly earliestReleaseDate?: string;
97
+ };
98
+ readonly build?: {
99
+ readonly id: string;
100
+ readonly buildNumber: string;
101
+ readonly action: 'attach' | 'unchanged';
102
+ /** The build attached now, which the new one replaces. */
103
+ readonly replaces?: string;
104
+ };
105
+ readonly whatsNew: readonly {
106
+ readonly locale: string;
107
+ readonly localizationId: string;
108
+ readonly change: StorePresenceTextChange;
109
+ }[];
110
+ readonly reviewDetail: {
111
+ readonly action: 'create' | 'update' | 'unchanged';
112
+ readonly id?: string;
113
+ /** Field names only; the values stay in the game file and the environment. */
114
+ readonly fields: readonly string[];
115
+ };
116
+ readonly phasedRelease: {
117
+ readonly action: 'create' | 'delete' | 'unchanged';
118
+ readonly id?: string;
119
+ readonly state?: string;
120
+ };
121
+ readonly iaps: readonly PlannedReleaseIap[];
122
+ readonly submission: {
123
+ readonly action: 'create' | 'reuse' | 'submitted' | 'none';
124
+ readonly id?: string;
125
+ readonly state?: string;
126
+ readonly addVersion: boolean;
127
+ readonly submit: boolean;
128
+ };
129
+ /** The writes apply makes, in order. */
130
+ readonly operations: readonly string[];
131
+ /** What must be fixed before apply runs; apply refuses while any is listed. */
132
+ readonly blockers: readonly string[];
133
+ readonly notes: readonly string[];
134
+ /** What the kit cannot do; the developer does it in App Store Connect. */
135
+ readonly manual: readonly string[];
136
+ readonly changed: boolean;
137
+ readonly remoteSha256: string;
138
+ readonly desiredSha256: string;
139
+ }
140
+ /** Read App Store Connect and plan; never writes. */
141
+ export declare function planAppStoreRelease(input: AppStoreReleasePlanInput, ports: AppStoreReleasePorts): Promise<AppStoreReleasePlan>;
142
+ export interface AppStoreReleaseApplyResult {
143
+ readonly appId: string;
144
+ readonly marketingVersion: string;
145
+ readonly status: 'created-version' | 'submitted' | 'unchanged' | 'dry-run';
146
+ /** The writes, in order (dry run: the writes apply would make). */
147
+ readonly steps: readonly string[];
148
+ readonly versionId?: string;
149
+ readonly submissionId?: string;
150
+ readonly next: readonly string[];
151
+ readonly manual: readonly string[];
152
+ }
153
+ export declare class AppStoreReleaseApplyError extends Error {
154
+ readonly stage: string;
155
+ /** The writes that landed before the failure. */
156
+ readonly written: readonly string[];
157
+ /** App Store Connect refused the step (asc exited with a 4xx-class code): nothing from it landed. */
158
+ readonly definitive: boolean;
159
+ /** What to do instead of re-running the same plan, when re-running cannot help. */
160
+ readonly detail?: string | undefined;
161
+ constructor(stage: string,
162
+ /** The writes that landed before the failure. */
163
+ written: readonly string[], cause: unknown,
164
+ /** App Store Connect refused the step (asc exited with a 4xx-class code): nothing from it landed. */
165
+ definitive?: boolean,
166
+ /** What to do instead of re-running the same plan, when re-running cannot help. */
167
+ detail?: string | undefined);
168
+ }
169
+ /** Refuse a plan with blockers, with every blocker named. */
170
+ export declare function assertNoBlockers(plan: AppStoreReleasePlan): void;
171
+ /**
172
+ * Apply a reviewed plan: re-plan, refuse when App Store Connect or the files
173
+ * moved past it or it has blockers, then write step by step. A plan whose
174
+ * changes already landed is a no-op, so a re-run is safe. `dryRun` stops
175
+ * after the checks and returns the writes it would make.
176
+ */
177
+ export declare function applyAppStoreRelease(input: {
178
+ readonly saved: AppStoreReleasePlan;
179
+ readonly plan: AppStoreReleasePlanInput;
180
+ readonly ports: AppStoreReleasePorts;
181
+ readonly approved: boolean;
182
+ readonly dryRun?: boolean;
183
+ }): Promise<AppStoreReleaseApplyResult>;
184
+ /** The plan for the terminal. */
185
+ export declare function formatAppStoreReleasePlan(plan: AppStoreReleasePlan): string;
186
+ export interface AppStoreReleaseStatus {
187
+ readonly appId: string;
188
+ readonly version?: {
189
+ readonly id: string;
190
+ readonly versionString: string;
191
+ readonly state: string;
192
+ readonly releaseType?: string;
193
+ readonly earliestReleaseDate?: string;
194
+ readonly build?: {
195
+ readonly id: string;
196
+ readonly buildNumber?: string;
197
+ };
198
+ };
199
+ readonly submission?: {
200
+ readonly id: string;
201
+ readonly state: string;
202
+ readonly submittedDate?: string;
203
+ readonly items: readonly AscReviewSubmissionItem[];
204
+ };
205
+ readonly phasedRelease?: AscPhasedRelease;
206
+ /** Where the release stands and what comes next, in a sentence. */
207
+ readonly summary: string;
208
+ }
209
+ /** Where a version stands: its state, build, review submission and phased release. Reads only. */
210
+ export declare function readAppStoreReleaseStatus(input: {
211
+ readonly appId: string;
212
+ readonly bundleId: string;
213
+ readonly marketingVersion?: string;
214
+ }, port: AscReleasePort): Promise<AppStoreReleaseStatus>;
215
+ /** Release an approved version that waits for a manual release. */
216
+ export declare function publishAppStoreVersion(input: {
217
+ readonly appId: string;
218
+ readonly bundleId: string;
219
+ readonly marketingVersion: string;
220
+ readonly approved: boolean;
221
+ }, port: AscReleasePort): Promise<{
222
+ readonly status: 'released' | 'unchanged';
223
+ readonly versionId: string;
224
+ readonly state: string;
225
+ }>;
226
+ export type PhasedReleaseAction = 'pause' | 'resume' | 'complete';
227
+ /** Pause, resume or complete the phased release of a released version. */
228
+ export declare function changeAppStorePhasedRelease(input: {
229
+ readonly appId: string;
230
+ readonly bundleId: string;
231
+ readonly marketingVersion: string;
232
+ readonly action: PhasedReleaseAction;
233
+ readonly approved: boolean;
234
+ }, port: AscReleasePort): Promise<{
235
+ readonly status: 'updated' | 'unchanged';
236
+ readonly phasedReleaseId: string;
237
+ readonly state: string;
238
+ }>;