@mpgd/cli 0.58.0 → 0.61.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.
- package/dist/capacitor-shell-starter.d.ts +24 -0
- package/dist/capacitor-shell-starter.js +100 -8
- package/dist/crash-reporting-starter.d.ts +99 -0
- package/dist/crash-reporting-starter.js +271 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +221 -14
- package/dist/ios-ipa-inspection.js +120 -1
- package/dist/native-build-info.json +2 -2
- package/dist/native-build-target.js +1617 -1185
- package/dist/native-deploy-run.d.ts +6 -0
- package/dist/native-deploy-run.js +19 -1
- package/dist/native-deploy-submission.d.ts +20 -1
- package/dist/native-deploy-submission.js +94 -7
- package/dist/native-shell-ios-push-files.d.ts +9 -0
- package/dist/native-shell-ios-push-files.js +65 -0
- package/dist/native-shell-store-policy.d.ts +109 -0
- package/dist/native-shell-store-policy.js +553 -7
- package/dist/play-internal-submission.d.ts +40 -4
- package/dist/play-internal-submission.js +215 -48
- package/dist/play-publisher-port.d.ts +26 -1
- package/dist/play-release-options.d.ts +97 -0
- package/dist/play-release-options.js +307 -0
- package/dist/play-release-promote.d.ts +59 -0
- package/dist/play-release-promote.js +235 -0
- package/dist/play-release-rollout.d.ts +57 -0
- package/dist/play-release-rollout.js +237 -0
- package/dist/play-sdk-adapter.js +26 -3
- package/dist/release-state.d.ts +6 -0
- package/dist/release-state.js +8 -0
- package/package.json +17 -17
- package/templates/phaser-game/vite.runtime-target-config.ts +4 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { isDefinitiveHttpError, isHttpStatus } from './play-internal-submission.js';
|
|
3
|
+
import { assertPlayReleaseConfirmed, assertPlayTrackName, assertPlayUserFraction, } from './play-release-options.js';
|
|
4
|
+
export const playRolloutActions = Object.freeze([
|
|
5
|
+
'update',
|
|
6
|
+
'halt',
|
|
7
|
+
'resume',
|
|
8
|
+
'complete',
|
|
9
|
+
]);
|
|
10
|
+
export class PlayRolloutUncertainError extends Error {
|
|
11
|
+
stage;
|
|
12
|
+
editId;
|
|
13
|
+
versionCodes;
|
|
14
|
+
constructor(stage, editId,
|
|
15
|
+
/** The release that was being changed; pass one of them as --version-code on the re-run. */
|
|
16
|
+
versionCodes = []) {
|
|
17
|
+
super(`Google Play rollout ${stage} result is uncertain for edit ${editId}. Re-run the same `
|
|
18
|
+
+ `command${versionCodes[0] === undefined ? '' : ` with --version-code ${versionCodes[0]}`}: `
|
|
19
|
+
+ 'it reads the live track first and reports "unchanged" if the change landed.');
|
|
20
|
+
this.stage = stage;
|
|
21
|
+
this.editId = editId;
|
|
22
|
+
this.versionCodes = versionCodes;
|
|
23
|
+
this.name = 'PlayRolloutUncertainError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Without --version-code, the release an action applies to (or has already
|
|
27
|
+
// been applied to, so a re-run after a lost response reports "unchanged").
|
|
28
|
+
const sourceStatuses = {
|
|
29
|
+
update: ['inProgress'],
|
|
30
|
+
halt: ['inProgress', 'halted'],
|
|
31
|
+
resume: ['halted', 'inProgress'],
|
|
32
|
+
complete: ['inProgress', 'halted'],
|
|
33
|
+
};
|
|
34
|
+
/** Change one existing release on one named track with a service account. */
|
|
35
|
+
export async function changePlayRollout(input) {
|
|
36
|
+
if (!existsSync(input.serviceAccountFile) || !statSync(input.serviceAccountFile).isFile()) {
|
|
37
|
+
throw new Error('Google Play service account file is missing.');
|
|
38
|
+
}
|
|
39
|
+
assertPlayRolloutInput(input);
|
|
40
|
+
const { createServiceAccountPublisher } = await import(new URL('./play-sdk-adapter.js', import.meta.url).href);
|
|
41
|
+
return changePlayRolloutWithPublisher(input, createServiceAccountPublisher(input.serviceAccountFile));
|
|
42
|
+
}
|
|
43
|
+
export function assertPlayRolloutInput(input) {
|
|
44
|
+
assertPlayTrackName(input.track);
|
|
45
|
+
if (!playRolloutActions.includes(input.action)) {
|
|
46
|
+
throw new Error('--action must be update, halt, resume or complete.');
|
|
47
|
+
}
|
|
48
|
+
if (input.versionCode !== undefined
|
|
49
|
+
&& (!Number.isSafeInteger(input.versionCode) || input.versionCode < 1)) {
|
|
50
|
+
throw new Error('--version-code must be a positive integer.');
|
|
51
|
+
}
|
|
52
|
+
if (input.userFraction !== undefined) {
|
|
53
|
+
if (input.action !== 'update' && input.action !== 'resume') {
|
|
54
|
+
throw new Error('--user-fraction applies only to --action update or resume.');
|
|
55
|
+
}
|
|
56
|
+
assertPlayUserFraction(input.userFraction);
|
|
57
|
+
}
|
|
58
|
+
else if (input.action === 'update') {
|
|
59
|
+
throw new Error('--action update needs --user-fraction.');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Credential-free injection point for the mock-transport tests. */
|
|
63
|
+
export async function changePlayRolloutWithPublisher(input, publisher) {
|
|
64
|
+
assertPlayRolloutInput(input);
|
|
65
|
+
if (input.dryRun !== true && (input.action === 'update' || input.action === 'resume')) {
|
|
66
|
+
// Leaves (or keeps) a staged rollout running: confirm before any request.
|
|
67
|
+
assertPlayReleaseConfirmed({ track: input.track, status: 'inProgress' }, input.confirmProduction === true);
|
|
68
|
+
}
|
|
69
|
+
else if (input.dryRun !== true) {
|
|
70
|
+
assertPlayReleaseConfirmed({ track: input.track, status: 'completed' }, input.confirmProduction === true);
|
|
71
|
+
}
|
|
72
|
+
input.signal?.throwIfAborted();
|
|
73
|
+
const { packageName, track: trackName } = input;
|
|
74
|
+
const editId = await publisher.insertEdit(packageName, input.signal);
|
|
75
|
+
let committed = false;
|
|
76
|
+
try {
|
|
77
|
+
let current;
|
|
78
|
+
try {
|
|
79
|
+
current = await publisher.getTrack(packageName, editId, trackName, input.signal);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (isHttpStatus(error, 404)) {
|
|
83
|
+
throw new Error(`Google Play has no ${trackName} track for ${packageName}.`);
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
const releases = current.releases ?? [];
|
|
88
|
+
const target = selectRelease(releases, input);
|
|
89
|
+
const before = releaseState(target);
|
|
90
|
+
const changed = applyAction(target, input);
|
|
91
|
+
const versionCodes = [...(target.versionCodes ?? [])];
|
|
92
|
+
if (changed === undefined) {
|
|
93
|
+
return {
|
|
94
|
+
status: 'unchanged',
|
|
95
|
+
packageName,
|
|
96
|
+
track: trackName,
|
|
97
|
+
action: input.action,
|
|
98
|
+
versionCodes,
|
|
99
|
+
before,
|
|
100
|
+
after: before,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// Only the selected release changes; every other release goes back as Play returned it.
|
|
104
|
+
const desired = {
|
|
105
|
+
track: trackName,
|
|
106
|
+
releases: releases.map((release) => release === target ? changed : release),
|
|
107
|
+
};
|
|
108
|
+
const after = releaseState(changed);
|
|
109
|
+
if (input.dryRun === true) {
|
|
110
|
+
return {
|
|
111
|
+
status: 'dry-run',
|
|
112
|
+
packageName,
|
|
113
|
+
track: trackName,
|
|
114
|
+
action: input.action,
|
|
115
|
+
versionCodes,
|
|
116
|
+
before,
|
|
117
|
+
after,
|
|
118
|
+
plannedTrack: desired,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
await publisher.updateTrack(packageName, editId, desired, input.signal);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (isDefinitiveHttpError(error)) {
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
throw new PlayRolloutUncertainError('track-update', editId, versionCodes);
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
await publisher.validateEdit(packageName, editId, input.signal);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (isDefinitiveHttpError(error)) {
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
throw new PlayRolloutUncertainError('validate', editId, versionCodes);
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
await publisher.commitEdit(packageName, editId, input.signal);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (isDefinitiveHttpError(error)) {
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
committed = true; // Unknown: never delete an edit that may have been committed.
|
|
147
|
+
throw new PlayRolloutUncertainError('commit', editId, versionCodes);
|
|
148
|
+
}
|
|
149
|
+
committed = true;
|
|
150
|
+
return {
|
|
151
|
+
status: 'committed',
|
|
152
|
+
packageName,
|
|
153
|
+
track: trackName,
|
|
154
|
+
action: input.action,
|
|
155
|
+
versionCodes,
|
|
156
|
+
before,
|
|
157
|
+
after,
|
|
158
|
+
editId,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
if (!committed) {
|
|
163
|
+
// Discard the uncommitted edit (dry run, no-op or refusal); a failure here is harmless.
|
|
164
|
+
await publisher.deleteEdit(packageName, editId, input.signal).catch(() => undefined);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function selectRelease(releases, input) {
|
|
169
|
+
const candidates = input.versionCode === undefined
|
|
170
|
+
? releases.filter((release) => sourceStatuses[input.action].includes(String(release.status)))
|
|
171
|
+
: releases.filter((release) => release.versionCodes?.includes(String(input.versionCode)));
|
|
172
|
+
if (candidates.length !== 1 || candidates[0] === undefined) {
|
|
173
|
+
const listed = releases.map((release) => `${String(release.status)} [${(release.versionCodes ?? []).join(',')}]`).join('; ') || 'none';
|
|
174
|
+
throw new Error(input.versionCode === undefined
|
|
175
|
+
? `Google Play ${input.track} needs exactly one ${sourceStatuses[input.action].join(' or ')} `
|
|
176
|
+
+ `release for --action ${input.action}; pass --version-code. Releases: ${listed}.`
|
|
177
|
+
: `Google Play ${input.track} has no single release with version code ${input.versionCode}. `
|
|
178
|
+
+ `Releases: ${listed}.`);
|
|
179
|
+
}
|
|
180
|
+
return candidates[0];
|
|
181
|
+
}
|
|
182
|
+
/** The changed release, or undefined when it is already in the requested state. */
|
|
183
|
+
function applyAction(release, input) {
|
|
184
|
+
const status = String(release.status);
|
|
185
|
+
const fraction = typeof release.userFraction === 'number' ? release.userFraction : undefined;
|
|
186
|
+
const refuse = (detail) => {
|
|
187
|
+
throw new Error(`Google Play ${input.track} release is ${status}; ${detail}`);
|
|
188
|
+
};
|
|
189
|
+
const { userFraction: _dropped, ...withoutFraction } = release;
|
|
190
|
+
switch (input.action) {
|
|
191
|
+
case 'update':
|
|
192
|
+
if (status !== 'inProgress') {
|
|
193
|
+
return refuse('only an inProgress release has a fraction to update.');
|
|
194
|
+
}
|
|
195
|
+
return fraction === input.userFraction
|
|
196
|
+
? undefined
|
|
197
|
+
: { ...release, userFraction: input.userFraction };
|
|
198
|
+
case 'halt':
|
|
199
|
+
if (status === 'halted') {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
if (status !== 'inProgress' && status !== 'completed') {
|
|
203
|
+
return refuse('only an inProgress or completed release can be halted.');
|
|
204
|
+
}
|
|
205
|
+
// Halting a completed release serves the previous completed release instead.
|
|
206
|
+
return { ...release, status: 'halted' };
|
|
207
|
+
case 'resume': {
|
|
208
|
+
if (status === 'inProgress') {
|
|
209
|
+
return input.userFraction === undefined || input.userFraction === fraction
|
|
210
|
+
? undefined
|
|
211
|
+
: refuse('use --action update to change its fraction.');
|
|
212
|
+
}
|
|
213
|
+
if (status !== 'halted') {
|
|
214
|
+
return refuse('only a halted release can be resumed.');
|
|
215
|
+
}
|
|
216
|
+
const resumed = input.userFraction ?? fraction;
|
|
217
|
+
if (resumed === undefined) {
|
|
218
|
+
return refuse('it has no rollout fraction; pass --user-fraction or use --action complete.');
|
|
219
|
+
}
|
|
220
|
+
return { ...release, status: 'inProgress', userFraction: resumed };
|
|
221
|
+
}
|
|
222
|
+
case 'complete':
|
|
223
|
+
if (status === 'completed') {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
if (status !== 'inProgress' && status !== 'halted') {
|
|
227
|
+
return refuse('only an inProgress or halted release can be completed.');
|
|
228
|
+
}
|
|
229
|
+
return { ...withoutFraction, status: 'completed' };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function releaseState(release) {
|
|
233
|
+
return {
|
|
234
|
+
status: String(release.status),
|
|
235
|
+
...(typeof release.userFraction === 'number' ? { userFraction: release.userFraction } : {}),
|
|
236
|
+
};
|
|
237
|
+
}
|
package/dist/play-sdk-adapter.js
CHANGED
|
@@ -43,17 +43,34 @@ function wrapPublisher(publisher) {
|
|
|
43
43
|
});
|
|
44
44
|
return response.data;
|
|
45
45
|
},
|
|
46
|
-
async
|
|
46
|
+
async uploadDeobfuscationFile(input) {
|
|
47
|
+
await publisher.edits.deobfuscationfiles.upload({
|
|
48
|
+
packageName: input.packageName,
|
|
49
|
+
editId: input.editId,
|
|
50
|
+
apkVersionCode: input.versionCode,
|
|
51
|
+
deobfuscationFileType: "proguard",
|
|
52
|
+
media: { mimeType: "application/octet-stream", body: createReadStream(input.mappingFile) }
|
|
53
|
+
}, {
|
|
54
|
+
timeout: input.timeoutMs,
|
|
55
|
+
retry: false,
|
|
56
|
+
signal: input.signal,
|
|
57
|
+
...input.requestRootUrl === void 0 ? {} : { rootUrl: input.requestRootUrl }
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
async getTrack(packageName, editId, track, signal) {
|
|
47
61
|
return (await publisher.edits.tracks.get(
|
|
48
|
-
{ packageName, editId, track
|
|
62
|
+
{ packageName, editId, track },
|
|
49
63
|
{ signal }
|
|
50
64
|
)).data;
|
|
51
65
|
},
|
|
52
66
|
async updateTrack(packageName, editId, track, signal) {
|
|
67
|
+
if (typeof track.track !== "string" || track.track === "") {
|
|
68
|
+
throw new Error("Google Play track update needs a track name.");
|
|
69
|
+
}
|
|
53
70
|
await publisher.edits.tracks.update({
|
|
54
71
|
packageName,
|
|
55
72
|
editId,
|
|
56
|
-
track:
|
|
73
|
+
track: track.track,
|
|
57
74
|
requestBody: track
|
|
58
75
|
}, { retry: false, signal });
|
|
59
76
|
},
|
|
@@ -69,6 +86,12 @@ function wrapPublisher(publisher) {
|
|
|
69
86
|
editId,
|
|
70
87
|
changesInReviewBehavior: "ERROR_IF_IN_REVIEW"
|
|
71
88
|
}, { retry: false, signal });
|
|
89
|
+
},
|
|
90
|
+
async deleteEdit(packageName, editId, signal) {
|
|
91
|
+
await publisher.edits.delete(
|
|
92
|
+
{ packageName, editId },
|
|
93
|
+
{ retry: false, signal }
|
|
94
|
+
);
|
|
72
95
|
}
|
|
73
96
|
};
|
|
74
97
|
}
|
package/dist/release-state.d.ts
CHANGED
|
@@ -54,6 +54,12 @@ export interface NativeSubmissionCheckpoint {
|
|
|
54
54
|
readonly remoteEditId?: string;
|
|
55
55
|
readonly remoteUploadId?: string;
|
|
56
56
|
readonly remoteBuildId?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Android only: digest of the Google Play release options (track, status,
|
|
59
|
+
* fraction, notes, mapping) the attempt started with. Absent for the original
|
|
60
|
+
* internal/completed submission. It never changes once written.
|
|
61
|
+
*/
|
|
62
|
+
readonly playReleaseSha256?: string;
|
|
57
63
|
}
|
|
58
64
|
export interface NativeReleaseStatus {
|
|
59
65
|
readonly plan: PlatformVersionReleasePlan;
|
package/dist/release-state.js
CHANGED
|
@@ -410,6 +410,11 @@ export async function checkpointNativeSubmission(input) {
|
|
|
410
410
|
}
|
|
411
411
|
if (isNativeSubmissionSettled(previous.status)
|
|
412
412
|
|| previous.status === 'failed'
|
|
413
|
+
|| (previous.playReleaseSha256 !== input.checkpoint.playReleaseSha256
|
|
414
|
+
// Options may be corrected only before any remote edit exists.
|
|
415
|
+
&& !(previous.status === 'started' && input.checkpoint.status === 'started'
|
|
416
|
+
&& previous.remoteEditId === undefined
|
|
417
|
+
&& input.checkpoint.remoteEditId === undefined))
|
|
413
418
|
|| previous.remoteEditId !== undefined
|
|
414
419
|
&& previous.remoteEditId !== input.checkpoint.remoteEditId
|
|
415
420
|
&& !(previous.target === 'android'
|
|
@@ -872,6 +877,7 @@ const checkpointFields = [
|
|
|
872
877
|
'remoteEditId',
|
|
873
878
|
'remoteUploadId',
|
|
874
879
|
'remoteBuildId',
|
|
880
|
+
'playReleaseSha256',
|
|
875
881
|
];
|
|
876
882
|
/** Validate persisted checkpoint shape before any remote mutation or recovery. */
|
|
877
883
|
export function assertNativeSubmissionCheckpoint(value) {
|
|
@@ -907,6 +913,8 @@ export function assertNativeSubmissionCheckpoint(value) {
|
|
|
907
913
|
|| !Number.isFinite(Date.parse(value.leaseExpiresAt))
|
|
908
914
|
|| Object.keys(value).some((key) => !checkpointFields.includes(key))
|
|
909
915
|
|| !id('remoteEditId') || !id('remoteUploadId') || !id('remoteBuildId')
|
|
916
|
+
|| (value.playReleaseSha256 !== undefined
|
|
917
|
+
&& (value.target !== 'android' || !sha256Pattern.test(String(value.playReleaseSha256))))
|
|
910
918
|
|| (value.target === 'android' && (!androidStatuses.includes(value.status)
|
|
911
919
|
|| value.remoteUploadId !== undefined || value.remoteBuildId !== undefined
|
|
912
920
|
|| (['edit-open', 'committed'].includes(String(value.status))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.61.0",
|
|
4
4
|
"description": "Gunshi CLI for mpgd-kit starter generation and target workflow orchestration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -70,10 +70,10 @@
|
|
|
70
70
|
"parse5": "7.3.0",
|
|
71
71
|
"postcss": "8.5.28",
|
|
72
72
|
"postcss-value-parser": "4.2.0",
|
|
73
|
-
"@mpgd/
|
|
74
|
-
"@mpgd/
|
|
75
|
-
"@mpgd/
|
|
76
|
-
"@mpgd/
|
|
73
|
+
"@mpgd/catalog": "0.11.3",
|
|
74
|
+
"@mpgd/platform": "0.32.0",
|
|
75
|
+
"@mpgd/target-config": "0.24.1",
|
|
76
|
+
"@mpgd/phaser-assets": "0.9.1"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@types/node": "^24.0.0",
|
|
@@ -81,18 +81,18 @@
|
|
|
81
81
|
"@types/yauzl": "3.4.0",
|
|
82
82
|
"ttsc": "0.32.1",
|
|
83
83
|
"typescript": "7.0.2",
|
|
84
|
-
"@mpgd/adapter-ait": "0.
|
|
85
|
-
"@mpgd/adapter-
|
|
86
|
-
"@mpgd/adapter-
|
|
87
|
-
"@mpgd/adapter-verse8": "0.7.
|
|
88
|
-
"@mpgd/adapter-
|
|
89
|
-
"@mpgd/
|
|
90
|
-
"@mpgd/
|
|
91
|
-
"@mpgd/capacitor-play-billing": "0.
|
|
92
|
-
"@mpgd/capacitor-storekit": "0.
|
|
93
|
-
"@mpgd/game-runtime": "0.8.
|
|
94
|
-
"@mpgd/game-services": "0.
|
|
95
|
-
"@mpgd/i18n": "0.6.
|
|
84
|
+
"@mpgd/adapter-ait": "0.21.1",
|
|
85
|
+
"@mpgd/adapter-browser": "0.12.1",
|
|
86
|
+
"@mpgd/adapter-capacitor": "0.16.0",
|
|
87
|
+
"@mpgd/adapter-verse8": "0.7.13",
|
|
88
|
+
"@mpgd/adapter-devvit": "0.14.4",
|
|
89
|
+
"@mpgd/bridge": "0.16.4",
|
|
90
|
+
"@mpgd/analytics": "0.5.5",
|
|
91
|
+
"@mpgd/capacitor-play-billing": "0.4.2",
|
|
92
|
+
"@mpgd/capacitor-storekit": "0.4.2",
|
|
93
|
+
"@mpgd/game-runtime": "0.8.6",
|
|
94
|
+
"@mpgd/game-services": "0.30.2",
|
|
95
|
+
"@mpgd/i18n": "0.6.26",
|
|
96
96
|
"@mpgd/lite-shell": "0.1.0"
|
|
97
97
|
},
|
|
98
98
|
"main": "./dist/index.js",
|
|
@@ -68,6 +68,7 @@ const targetIntegrations = Object.keys({
|
|
|
68
68
|
sharing: true,
|
|
69
69
|
inboundShare: true,
|
|
70
70
|
notifications: true,
|
|
71
|
+
reviews: true,
|
|
71
72
|
} satisfies Record<TargetIntegration, true>);
|
|
72
73
|
|
|
73
74
|
export function assertRuntimeTargetConfigMatrix(input: unknown): TargetConfigMatrix {
|
|
@@ -142,6 +143,9 @@ function assertTargetConfig(input: unknown, label: string): asserts input is Tar
|
|
|
142
143
|
if (input.integrations !== undefined) {
|
|
143
144
|
assertRecord(input.integrations, `${label}.integrations`);
|
|
144
145
|
for (const key of targetIntegrations) {
|
|
146
|
+
if (key === 'reviews' && input.integrations[key] === undefined) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
145
149
|
assertOneOf(
|
|
146
150
|
input.integrations[key],
|
|
147
151
|
integrationAvailabilityValues,
|