@gpc-cli/core 0.9.8 → 0.9.9
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/index.d.ts +66 -2
- package/dist/index.js +689 -49
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -54,6 +54,8 @@ function formatOutput(data, format, redact = true) {
|
|
|
54
54
|
return formatMarkdown(safe);
|
|
55
55
|
case "table":
|
|
56
56
|
return formatTable(safe);
|
|
57
|
+
case "junit":
|
|
58
|
+
return formatJunit(safe);
|
|
57
59
|
default:
|
|
58
60
|
return formatJson(safe);
|
|
59
61
|
}
|
|
@@ -182,6 +184,73 @@ function toRows(data) {
|
|
|
182
184
|
}
|
|
183
185
|
return [];
|
|
184
186
|
}
|
|
187
|
+
function escapeXml(str) {
|
|
188
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
189
|
+
}
|
|
190
|
+
function toTestCases(data, commandName) {
|
|
191
|
+
const cases = [];
|
|
192
|
+
let failures = 0;
|
|
193
|
+
if (Array.isArray(data)) {
|
|
194
|
+
for (const item of data) {
|
|
195
|
+
const tc = buildTestCase(item, commandName);
|
|
196
|
+
cases.push(tc.xml);
|
|
197
|
+
if (tc.failed) failures++;
|
|
198
|
+
}
|
|
199
|
+
} else if (typeof data === "object" && data !== null) {
|
|
200
|
+
const tc = buildTestCase(data, commandName);
|
|
201
|
+
cases.push(tc.xml);
|
|
202
|
+
if (tc.failed) failures++;
|
|
203
|
+
} else if (typeof data === "string") {
|
|
204
|
+
cases.push(
|
|
205
|
+
` <testcase name="${escapeXml(data)}" classname="gpc.${escapeXml(commandName)}" />`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
return { cases, failures };
|
|
209
|
+
}
|
|
210
|
+
function buildTestCase(item, commandName) {
|
|
211
|
+
if (typeof item !== "object" || item === null) {
|
|
212
|
+
const text = String(item);
|
|
213
|
+
return {
|
|
214
|
+
xml: ` <testcase name="${escapeXml(text)}" classname="gpc.${escapeXml(commandName)}" />`,
|
|
215
|
+
failed: false
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const record = item;
|
|
219
|
+
const name = escapeXml(
|
|
220
|
+
String(record["name"] ?? record["title"] ?? record["sku"] ?? record["id"] ?? JSON.stringify(item))
|
|
221
|
+
);
|
|
222
|
+
const classname = `gpc.${escapeXml(commandName)}`;
|
|
223
|
+
const breached = record["breached"];
|
|
224
|
+
if (breached === true) {
|
|
225
|
+
const message = escapeXml(String(record["message"] ?? "threshold breached"));
|
|
226
|
+
const details = escapeXml(
|
|
227
|
+
String(record["details"] ?? record["metric"] ?? JSON.stringify(item))
|
|
228
|
+
);
|
|
229
|
+
return {
|
|
230
|
+
xml: ` <testcase name="${name}" classname="${classname}">
|
|
231
|
+
<failure message="${message}">${details}</failure>
|
|
232
|
+
</testcase>`,
|
|
233
|
+
failed: true
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
xml: ` <testcase name="${name}" classname="${classname}" />`,
|
|
238
|
+
failed: false
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function formatJunit(data, commandName = "command") {
|
|
242
|
+
const { cases, failures } = toTestCases(data, commandName);
|
|
243
|
+
const tests = cases.length;
|
|
244
|
+
const lines = [
|
|
245
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
246
|
+
`<testsuites name="gpc" tests="${tests}" failures="${failures}" time="0">`,
|
|
247
|
+
` <testsuite name="${escapeXml(commandName)}" tests="${tests}" failures="${failures}">`,
|
|
248
|
+
...cases,
|
|
249
|
+
" </testsuite>",
|
|
250
|
+
"</testsuites>"
|
|
251
|
+
];
|
|
252
|
+
return lines.join("\n");
|
|
253
|
+
}
|
|
185
254
|
|
|
186
255
|
// src/plugins.ts
|
|
187
256
|
var PluginManager = class {
|
|
@@ -682,6 +751,90 @@ async function listTracks(client, packageName) {
|
|
|
682
751
|
throw error;
|
|
683
752
|
}
|
|
684
753
|
}
|
|
754
|
+
async function createTrack(client, packageName, trackName) {
|
|
755
|
+
if (!trackName || trackName.trim().length === 0) {
|
|
756
|
+
throw new GpcError(
|
|
757
|
+
"Track name must not be empty",
|
|
758
|
+
"TRACK_INVALID_NAME",
|
|
759
|
+
2,
|
|
760
|
+
"Provide a valid custom track name, e.g.: gpc tracks create my-qa-track"
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
const edit = await client.edits.insert(packageName);
|
|
764
|
+
try {
|
|
765
|
+
const track = await client.tracks.create(packageName, edit.id, trackName);
|
|
766
|
+
await client.edits.validate(packageName, edit.id);
|
|
767
|
+
await client.edits.commit(packageName, edit.id);
|
|
768
|
+
return track;
|
|
769
|
+
} catch (error) {
|
|
770
|
+
await client.edits.delete(packageName, edit.id).catch(() => {
|
|
771
|
+
});
|
|
772
|
+
throw error;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
async function updateTrackConfig(client, packageName, trackName, config) {
|
|
776
|
+
if (!trackName || trackName.trim().length === 0) {
|
|
777
|
+
throw new GpcError(
|
|
778
|
+
"Track name must not be empty",
|
|
779
|
+
"TRACK_INVALID_NAME",
|
|
780
|
+
2,
|
|
781
|
+
"Provide a valid track name."
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
const edit = await client.edits.insert(packageName);
|
|
785
|
+
try {
|
|
786
|
+
const release = {
|
|
787
|
+
versionCodes: config["versionCodes"] || [],
|
|
788
|
+
status: config["status"] || "completed"
|
|
789
|
+
};
|
|
790
|
+
if (config["userFraction"] !== void 0) {
|
|
791
|
+
release.userFraction = config["userFraction"];
|
|
792
|
+
}
|
|
793
|
+
if (config["releaseNotes"]) {
|
|
794
|
+
release.releaseNotes = config["releaseNotes"];
|
|
795
|
+
}
|
|
796
|
+
if (config["name"]) {
|
|
797
|
+
release.name = config["name"];
|
|
798
|
+
}
|
|
799
|
+
const track = await client.tracks.update(packageName, edit.id, trackName, release);
|
|
800
|
+
await client.edits.validate(packageName, edit.id);
|
|
801
|
+
await client.edits.commit(packageName, edit.id);
|
|
802
|
+
return track;
|
|
803
|
+
} catch (error) {
|
|
804
|
+
await client.edits.delete(packageName, edit.id).catch(() => {
|
|
805
|
+
});
|
|
806
|
+
throw error;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
async function uploadExternallyHosted(client, packageName, data) {
|
|
810
|
+
if (!data.externallyHostedUrl) {
|
|
811
|
+
throw new GpcError(
|
|
812
|
+
"externallyHostedUrl is required",
|
|
813
|
+
"EXTERNAL_APK_MISSING_URL",
|
|
814
|
+
2,
|
|
815
|
+
"Provide a valid URL for the externally hosted APK."
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
if (!data.packageName) {
|
|
819
|
+
throw new GpcError(
|
|
820
|
+
"packageName is required in externally hosted APK data",
|
|
821
|
+
"EXTERNAL_APK_MISSING_PACKAGE",
|
|
822
|
+
2,
|
|
823
|
+
"Include the packageName field in the APK configuration."
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
const edit = await client.edits.insert(packageName);
|
|
827
|
+
try {
|
|
828
|
+
const result = await client.apks.addExternallyHosted(packageName, edit.id, data);
|
|
829
|
+
await client.edits.validate(packageName, edit.id);
|
|
830
|
+
await client.edits.commit(packageName, edit.id);
|
|
831
|
+
return result;
|
|
832
|
+
} catch (error) {
|
|
833
|
+
await client.edits.delete(packageName, edit.id).catch(() => {
|
|
834
|
+
});
|
|
835
|
+
throw error;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
685
838
|
|
|
686
839
|
// src/utils/bcp47.ts
|
|
687
840
|
var GOOGLE_PLAY_LANGUAGES = [
|
|
@@ -1111,6 +1264,75 @@ async function getCountryAvailability(client, packageName, track) {
|
|
|
1111
1264
|
throw error;
|
|
1112
1265
|
}
|
|
1113
1266
|
}
|
|
1267
|
+
var ALL_IMAGE_TYPES = [
|
|
1268
|
+
"phoneScreenshots",
|
|
1269
|
+
"sevenInchScreenshots",
|
|
1270
|
+
"tenInchScreenshots",
|
|
1271
|
+
"tvScreenshots",
|
|
1272
|
+
"wearScreenshots",
|
|
1273
|
+
"icon",
|
|
1274
|
+
"featureGraphic",
|
|
1275
|
+
"tvBanner"
|
|
1276
|
+
];
|
|
1277
|
+
async function exportImages(client, packageName, dir, options) {
|
|
1278
|
+
const { mkdir: mkdir5, writeFile: writeFile6 } = await import("fs/promises");
|
|
1279
|
+
const { join: join7 } = await import("path");
|
|
1280
|
+
const edit = await client.edits.insert(packageName);
|
|
1281
|
+
try {
|
|
1282
|
+
let languages;
|
|
1283
|
+
if (options?.lang) {
|
|
1284
|
+
validateLanguage(options.lang);
|
|
1285
|
+
languages = [options.lang];
|
|
1286
|
+
} else {
|
|
1287
|
+
const listings = await client.listings.list(packageName, edit.id);
|
|
1288
|
+
languages = listings.map((l) => l.language);
|
|
1289
|
+
}
|
|
1290
|
+
const imageTypes = options?.type ? [options.type] : ALL_IMAGE_TYPES;
|
|
1291
|
+
let totalImages = 0;
|
|
1292
|
+
let totalSize = 0;
|
|
1293
|
+
const tasks = [];
|
|
1294
|
+
for (const language of languages) {
|
|
1295
|
+
for (const imageType of imageTypes) {
|
|
1296
|
+
const images = await client.images.list(packageName, edit.id, language, imageType);
|
|
1297
|
+
for (let i = 0; i < images.length; i++) {
|
|
1298
|
+
const img = images[i];
|
|
1299
|
+
if (img && img.url) {
|
|
1300
|
+
tasks.push({ language, imageType, url: img.url, index: i + 1 });
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
const concurrency = 5;
|
|
1306
|
+
for (let i = 0; i < tasks.length; i += concurrency) {
|
|
1307
|
+
const batch = tasks.slice(i, i + concurrency);
|
|
1308
|
+
const results = await Promise.all(
|
|
1309
|
+
batch.map(async (task) => {
|
|
1310
|
+
const dirPath = join7(dir, task.language, task.imageType);
|
|
1311
|
+
await mkdir5(dirPath, { recursive: true });
|
|
1312
|
+
const response = await fetch(task.url);
|
|
1313
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
1314
|
+
const filePath = join7(dirPath, `${task.index}.png`);
|
|
1315
|
+
await writeFile6(filePath, buffer);
|
|
1316
|
+
return buffer.length;
|
|
1317
|
+
})
|
|
1318
|
+
);
|
|
1319
|
+
for (const size of results) {
|
|
1320
|
+
totalImages++;
|
|
1321
|
+
totalSize += size;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
await client.edits.delete(packageName, edit.id);
|
|
1325
|
+
return {
|
|
1326
|
+
languages: languages.length,
|
|
1327
|
+
images: totalImages,
|
|
1328
|
+
totalSize
|
|
1329
|
+
};
|
|
1330
|
+
} catch (error) {
|
|
1331
|
+
await client.edits.delete(packageName, edit.id).catch(() => {
|
|
1332
|
+
});
|
|
1333
|
+
throw error;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1114
1336
|
async function updateAppDetails(client, packageName, details) {
|
|
1115
1337
|
const edit = await client.edits.insert(packageName);
|
|
1116
1338
|
try {
|
|
@@ -1125,14 +1347,202 @@ async function updateAppDetails(client, packageName, details) {
|
|
|
1125
1347
|
}
|
|
1126
1348
|
}
|
|
1127
1349
|
|
|
1350
|
+
// src/commands/migrate.ts
|
|
1351
|
+
import { readdir as readdir2, readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2, access } from "fs/promises";
|
|
1352
|
+
import { join as join2 } from "path";
|
|
1353
|
+
async function fileExists(path) {
|
|
1354
|
+
try {
|
|
1355
|
+
await access(path);
|
|
1356
|
+
return true;
|
|
1357
|
+
} catch {
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
async function detectFastlane(cwd) {
|
|
1362
|
+
const result = {
|
|
1363
|
+
hasFastfile: false,
|
|
1364
|
+
hasAppfile: false,
|
|
1365
|
+
hasMetadata: false,
|
|
1366
|
+
hasGemfile: false,
|
|
1367
|
+
lanes: [],
|
|
1368
|
+
metadataLanguages: []
|
|
1369
|
+
};
|
|
1370
|
+
const fastlaneDir = join2(cwd, "fastlane");
|
|
1371
|
+
const hasFastlaneDir = await fileExists(fastlaneDir);
|
|
1372
|
+
const fastfilePath = hasFastlaneDir ? join2(fastlaneDir, "Fastfile") : join2(cwd, "Fastfile");
|
|
1373
|
+
const appfilePath = hasFastlaneDir ? join2(fastlaneDir, "Appfile") : join2(cwd, "Appfile");
|
|
1374
|
+
result.hasFastfile = await fileExists(fastfilePath);
|
|
1375
|
+
result.hasAppfile = await fileExists(appfilePath);
|
|
1376
|
+
result.hasGemfile = await fileExists(join2(cwd, "Gemfile"));
|
|
1377
|
+
const metadataDir = hasFastlaneDir ? join2(fastlaneDir, "metadata", "android") : join2(cwd, "metadata", "android");
|
|
1378
|
+
result.hasMetadata = await fileExists(metadataDir);
|
|
1379
|
+
if (result.hasMetadata) {
|
|
1380
|
+
try {
|
|
1381
|
+
const entries = await readdir2(metadataDir, { withFileTypes: true });
|
|
1382
|
+
result.metadataLanguages = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1383
|
+
} catch {
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
if (result.hasFastfile) {
|
|
1387
|
+
try {
|
|
1388
|
+
const content = await readFile3(fastfilePath, "utf-8");
|
|
1389
|
+
result.lanes = parseFastfile(content);
|
|
1390
|
+
} catch {
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
if (result.hasAppfile) {
|
|
1394
|
+
try {
|
|
1395
|
+
const content = await readFile3(appfilePath, "utf-8");
|
|
1396
|
+
const parsed = parseAppfile(content);
|
|
1397
|
+
result.packageName = parsed.packageName;
|
|
1398
|
+
result.jsonKeyPath = parsed.jsonKeyPath;
|
|
1399
|
+
} catch {
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
return result;
|
|
1403
|
+
}
|
|
1404
|
+
function parseFastfile(content) {
|
|
1405
|
+
const lanes = [];
|
|
1406
|
+
const laneRegex = /lane\s+:(\w+)\s+do([\s\S]*?)(?=\bend\b)/g;
|
|
1407
|
+
let match;
|
|
1408
|
+
while ((match = laneRegex.exec(content)) !== null) {
|
|
1409
|
+
const name = match[1] ?? "";
|
|
1410
|
+
const body = match[2] ?? "";
|
|
1411
|
+
const actions = [];
|
|
1412
|
+
const actionRegex = /\b(supply|upload_to_play_store|capture_android_screenshots|deliver|gradle)\b/g;
|
|
1413
|
+
let actionMatch;
|
|
1414
|
+
while ((actionMatch = actionRegex.exec(body)) !== null) {
|
|
1415
|
+
const action = actionMatch[1] ?? "";
|
|
1416
|
+
if (!actions.includes(action)) {
|
|
1417
|
+
actions.push(action);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
const gpcEquivalent = mapLaneToGpc(name, actions, body);
|
|
1421
|
+
lanes.push({ name, actions, gpcEquivalent });
|
|
1422
|
+
}
|
|
1423
|
+
return lanes;
|
|
1424
|
+
}
|
|
1425
|
+
function mapLaneToGpc(name, actions, body) {
|
|
1426
|
+
if (actions.includes("upload_to_play_store") || actions.includes("supply")) {
|
|
1427
|
+
const trackMatch = body.match(/track\s*:\s*["'](\w+)["']/);
|
|
1428
|
+
const rolloutMatch = body.match(/rollout\s*:\s*["']?([\d.]+)["']?/);
|
|
1429
|
+
if (rolloutMatch) {
|
|
1430
|
+
const percentage = Math.round(parseFloat(rolloutMatch[1] ?? "0") * 100);
|
|
1431
|
+
return `gpc releases promote --rollout ${percentage}`;
|
|
1432
|
+
}
|
|
1433
|
+
if (trackMatch) {
|
|
1434
|
+
return `gpc releases upload --track ${trackMatch[1]}`;
|
|
1435
|
+
}
|
|
1436
|
+
if (body.match(/skip_upload_apk\s*:\s*true/) || body.match(/skip_upload_aab\s*:\s*true/)) {
|
|
1437
|
+
return "gpc listings push";
|
|
1438
|
+
}
|
|
1439
|
+
return "gpc releases upload";
|
|
1440
|
+
}
|
|
1441
|
+
if (actions.includes("capture_android_screenshots")) {
|
|
1442
|
+
return void 0;
|
|
1443
|
+
}
|
|
1444
|
+
return void 0;
|
|
1445
|
+
}
|
|
1446
|
+
function parseAppfile(content) {
|
|
1447
|
+
const result = {};
|
|
1448
|
+
const pkgMatch = content.match(/package_name\s*\(?\s*["']([^"']+)["']\s*\)?/);
|
|
1449
|
+
if (pkgMatch) {
|
|
1450
|
+
result.packageName = pkgMatch[1];
|
|
1451
|
+
}
|
|
1452
|
+
const keyMatch = content.match(/json_key_file\s*\(?\s*["']([^"']+)["']\s*\)?/);
|
|
1453
|
+
if (keyMatch) {
|
|
1454
|
+
result.jsonKeyPath = keyMatch[1];
|
|
1455
|
+
}
|
|
1456
|
+
return result;
|
|
1457
|
+
}
|
|
1458
|
+
function generateMigrationPlan(detection) {
|
|
1459
|
+
const config = {};
|
|
1460
|
+
const checklist = [];
|
|
1461
|
+
const warnings = [];
|
|
1462
|
+
if (detection.packageName) {
|
|
1463
|
+
config["app"] = detection.packageName;
|
|
1464
|
+
} else {
|
|
1465
|
+
checklist.push("Set your package name: gpc config set app <package-name>");
|
|
1466
|
+
}
|
|
1467
|
+
if (detection.jsonKeyPath) {
|
|
1468
|
+
config["auth"] = { serviceAccount: detection.jsonKeyPath };
|
|
1469
|
+
} else {
|
|
1470
|
+
checklist.push("Configure authentication: gpc auth setup");
|
|
1471
|
+
}
|
|
1472
|
+
for (const lane of detection.lanes) {
|
|
1473
|
+
if (lane.gpcEquivalent) {
|
|
1474
|
+
checklist.push(`Replace Fastlane lane "${lane.name}" with: ${lane.gpcEquivalent}`);
|
|
1475
|
+
}
|
|
1476
|
+
if (lane.actions.includes("capture_android_screenshots")) {
|
|
1477
|
+
warnings.push(
|
|
1478
|
+
`Lane "${lane.name}" uses capture_android_screenshots which has no GPC equivalent. You will need to continue using Fastlane for screenshot capture or use a separate tool.`
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
if (detection.hasMetadata && detection.metadataLanguages.length > 0) {
|
|
1483
|
+
checklist.push(
|
|
1484
|
+
`Migrate metadata for ${detection.metadataLanguages.length} language(s): gpc listings pull --dir metadata`
|
|
1485
|
+
);
|
|
1486
|
+
checklist.push("Review and push metadata: gpc listings push --dir metadata");
|
|
1487
|
+
}
|
|
1488
|
+
checklist.push("Run gpc doctor to verify your setup");
|
|
1489
|
+
checklist.push("Test with --dry-run before making real changes");
|
|
1490
|
+
if (detection.hasGemfile) {
|
|
1491
|
+
checklist.push("Remove Fastlane from your Gemfile once migration is complete");
|
|
1492
|
+
}
|
|
1493
|
+
if (detection.lanes.some((l) => l.actions.includes("supply") || l.actions.includes("upload_to_play_store"))) {
|
|
1494
|
+
checklist.push("Update CI/CD pipelines to use gpc commands instead of Fastlane lanes");
|
|
1495
|
+
}
|
|
1496
|
+
return { config, checklist, warnings };
|
|
1497
|
+
}
|
|
1498
|
+
async function writeMigrationOutput(result, dir) {
|
|
1499
|
+
await mkdir2(dir, { recursive: true });
|
|
1500
|
+
const files = [];
|
|
1501
|
+
const configPath = join2(dir, ".gpcrc.json");
|
|
1502
|
+
await writeFile2(configPath, JSON.stringify(result.config, null, 2) + "\n", "utf-8");
|
|
1503
|
+
files.push(configPath);
|
|
1504
|
+
const migrationPath = join2(dir, "MIGRATION.md");
|
|
1505
|
+
const lines = [
|
|
1506
|
+
"# Fastlane to GPC Migration",
|
|
1507
|
+
"",
|
|
1508
|
+
"## Migration Checklist",
|
|
1509
|
+
""
|
|
1510
|
+
];
|
|
1511
|
+
for (const item of result.checklist) {
|
|
1512
|
+
lines.push(`- [ ] ${item}`);
|
|
1513
|
+
}
|
|
1514
|
+
if (result.warnings.length > 0) {
|
|
1515
|
+
lines.push("");
|
|
1516
|
+
lines.push("## Warnings");
|
|
1517
|
+
lines.push("");
|
|
1518
|
+
for (const warning of result.warnings) {
|
|
1519
|
+
lines.push(`- ${warning}`);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
lines.push("");
|
|
1523
|
+
lines.push("## Quick Reference");
|
|
1524
|
+
lines.push("");
|
|
1525
|
+
lines.push("| Fastlane | GPC |");
|
|
1526
|
+
lines.push("|----------|-----|");
|
|
1527
|
+
lines.push("| `fastlane supply` | `gpc releases upload` / `gpc listings push` |");
|
|
1528
|
+
lines.push("| `upload_to_play_store` | `gpc releases upload` |");
|
|
1529
|
+
lines.push('| `supply(track: "internal")` | `gpc releases upload --track internal` |');
|
|
1530
|
+
lines.push('| `supply(rollout: "0.1")` | `gpc releases promote --rollout 10` |');
|
|
1531
|
+
lines.push("| `capture_android_screenshots` | No equivalent (use separate tool) |");
|
|
1532
|
+
lines.push("");
|
|
1533
|
+
await writeFile2(migrationPath, lines.join("\n"), "utf-8");
|
|
1534
|
+
files.push(migrationPath);
|
|
1535
|
+
return files;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1128
1538
|
// src/utils/release-notes.ts
|
|
1129
|
-
import { readdir as
|
|
1130
|
-
import { extname as extname3, basename, join as
|
|
1539
|
+
import { readdir as readdir3, readFile as readFile4, stat as stat4 } from "fs/promises";
|
|
1540
|
+
import { extname as extname3, basename, join as join3 } from "path";
|
|
1131
1541
|
var MAX_NOTES_LENGTH = 500;
|
|
1132
1542
|
async function readReleaseNotesFromDir(dir) {
|
|
1133
1543
|
let entries;
|
|
1134
1544
|
try {
|
|
1135
|
-
entries = await
|
|
1545
|
+
entries = await readdir3(dir);
|
|
1136
1546
|
} catch {
|
|
1137
1547
|
throw new GpcError(
|
|
1138
1548
|
`Release notes directory not found: ${dir}`,
|
|
@@ -1145,10 +1555,10 @@ async function readReleaseNotesFromDir(dir) {
|
|
|
1145
1555
|
for (const entry of entries) {
|
|
1146
1556
|
if (extname3(entry) !== ".txt") continue;
|
|
1147
1557
|
const language = basename(entry, ".txt");
|
|
1148
|
-
const filePath =
|
|
1558
|
+
const filePath = join3(dir, entry);
|
|
1149
1559
|
const stats = await stat4(filePath);
|
|
1150
1560
|
if (!stats.isFile()) continue;
|
|
1151
|
-
const text = (await
|
|
1561
|
+
const text = (await readFile4(filePath, "utf-8")).trim();
|
|
1152
1562
|
if (text.length === 0) continue;
|
|
1153
1563
|
notes.push({ language, text });
|
|
1154
1564
|
}
|
|
@@ -1633,8 +2043,8 @@ async function deactivateOffer(client, packageName, productId, basePlanId, offer
|
|
|
1633
2043
|
}
|
|
1634
2044
|
|
|
1635
2045
|
// src/commands/iap.ts
|
|
1636
|
-
import { readdir as
|
|
1637
|
-
import { join as
|
|
2046
|
+
import { readdir as readdir4, readFile as readFile5 } from "fs/promises";
|
|
2047
|
+
import { join as join4 } from "path";
|
|
1638
2048
|
import { paginateAll as paginateAll3 } from "@gpc-cli/api";
|
|
1639
2049
|
async function listInAppProducts(client, packageName, options) {
|
|
1640
2050
|
if (options?.limit || options?.nextPage) {
|
|
@@ -1671,14 +2081,59 @@ async function deleteInAppProduct(client, packageName, sku) {
|
|
|
1671
2081
|
return client.inappproducts.delete(packageName, sku);
|
|
1672
2082
|
}
|
|
1673
2083
|
async function syncInAppProducts(client, packageName, dir, options) {
|
|
1674
|
-
const
|
|
1675
|
-
|
|
1676
|
-
if (jsonFiles.length === 0) {
|
|
2084
|
+
const localProducts = await readProductsFromDir(dir);
|
|
2085
|
+
if (localProducts.length === 0) {
|
|
1677
2086
|
return { created: 0, updated: 0, unchanged: 0, skus: [] };
|
|
1678
2087
|
}
|
|
2088
|
+
const response = await client.inappproducts.list(packageName);
|
|
2089
|
+
const remoteSkus = new Set((response.inappproduct || []).map((p) => p.sku));
|
|
2090
|
+
const toUpdate = localProducts.filter((p) => remoteSkus.has(p.sku));
|
|
2091
|
+
const toCreate = localProducts.filter((p) => !remoteSkus.has(p.sku));
|
|
2092
|
+
const skus = localProducts.map((p) => p.sku);
|
|
2093
|
+
if (options?.dryRun) {
|
|
2094
|
+
return { created: toCreate.length, updated: toUpdate.length, unchanged: 0, skus };
|
|
2095
|
+
}
|
|
2096
|
+
if (toUpdate.length > 1) {
|
|
2097
|
+
try {
|
|
2098
|
+
await batchUpdateProducts(client, packageName, toUpdate);
|
|
2099
|
+
} catch {
|
|
2100
|
+
for (const product of toUpdate) {
|
|
2101
|
+
await client.inappproducts.update(packageName, product.sku, product);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
} else {
|
|
2105
|
+
for (const product of toUpdate) {
|
|
2106
|
+
await client.inappproducts.update(packageName, product.sku, product);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
for (const product of toCreate) {
|
|
2110
|
+
await client.inappproducts.create(packageName, product);
|
|
2111
|
+
}
|
|
2112
|
+
return { created: toCreate.length, updated: toUpdate.length, unchanged: 0, skus };
|
|
2113
|
+
}
|
|
2114
|
+
var BATCH_CHUNK_SIZE = 100;
|
|
2115
|
+
async function batchUpdateProducts(client, packageName, products) {
|
|
2116
|
+
const results = [];
|
|
2117
|
+
for (let i = 0; i < products.length; i += BATCH_CHUNK_SIZE) {
|
|
2118
|
+
const chunk = products.slice(i, i + BATCH_CHUNK_SIZE);
|
|
2119
|
+
const request = {
|
|
2120
|
+
requests: chunk.map((p) => ({
|
|
2121
|
+
inappproduct: p,
|
|
2122
|
+
packageName,
|
|
2123
|
+
sku: p.sku
|
|
2124
|
+
}))
|
|
2125
|
+
};
|
|
2126
|
+
const response = await client.inappproducts.batchUpdate(packageName, request);
|
|
2127
|
+
results.push(...response.inappproducts || []);
|
|
2128
|
+
}
|
|
2129
|
+
return results;
|
|
2130
|
+
}
|
|
2131
|
+
async function readProductsFromDir(dir) {
|
|
2132
|
+
const files = await readdir4(dir);
|
|
2133
|
+
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
1679
2134
|
const localProducts = [];
|
|
1680
2135
|
for (const file of jsonFiles) {
|
|
1681
|
-
const content = await
|
|
2136
|
+
const content = await readFile5(join4(dir, file), "utf-8");
|
|
1682
2137
|
try {
|
|
1683
2138
|
localProducts.push(JSON.parse(content));
|
|
1684
2139
|
} catch {
|
|
@@ -1690,27 +2145,56 @@ async function syncInAppProducts(client, packageName, dir, options) {
|
|
|
1690
2145
|
);
|
|
1691
2146
|
}
|
|
1692
2147
|
}
|
|
2148
|
+
return localProducts;
|
|
2149
|
+
}
|
|
2150
|
+
async function batchSyncInAppProducts(client, packageName, dir, options) {
|
|
2151
|
+
const localProducts = await readProductsFromDir(dir);
|
|
2152
|
+
if (localProducts.length === 0) {
|
|
2153
|
+
return { created: 0, updated: 0, unchanged: 0, skus: [], batchUsed: false, batchErrors: 0 };
|
|
2154
|
+
}
|
|
1693
2155
|
const response = await client.inappproducts.list(packageName);
|
|
1694
2156
|
const remoteSkus = new Set((response.inappproduct || []).map((p) => p.sku));
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
const
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
2157
|
+
const toUpdate = localProducts.filter((p) => remoteSkus.has(p.sku));
|
|
2158
|
+
const toCreate = localProducts.filter((p) => !remoteSkus.has(p.sku));
|
|
2159
|
+
const skus = localProducts.map((p) => p.sku);
|
|
2160
|
+
if (options?.dryRun) {
|
|
2161
|
+
return {
|
|
2162
|
+
created: toCreate.length,
|
|
2163
|
+
updated: toUpdate.length,
|
|
2164
|
+
unchanged: 0,
|
|
2165
|
+
skus,
|
|
2166
|
+
batchUsed: toUpdate.length > 1,
|
|
2167
|
+
batchErrors: 0
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
let batchUsed = false;
|
|
2171
|
+
let batchErrors = 0;
|
|
2172
|
+
if (toUpdate.length > 1) {
|
|
2173
|
+
batchUsed = true;
|
|
2174
|
+
try {
|
|
2175
|
+
await batchUpdateProducts(client, packageName, toUpdate);
|
|
2176
|
+
} catch {
|
|
2177
|
+
batchErrors++;
|
|
2178
|
+
for (const product of toUpdate) {
|
|
1703
2179
|
await client.inappproducts.update(packageName, product.sku, product);
|
|
1704
2180
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
}
|
|
1710
|
-
created++;
|
|
2181
|
+
}
|
|
2182
|
+
} else {
|
|
2183
|
+
for (const product of toUpdate) {
|
|
2184
|
+
await client.inappproducts.update(packageName, product.sku, product);
|
|
1711
2185
|
}
|
|
1712
2186
|
}
|
|
1713
|
-
|
|
2187
|
+
for (const product of toCreate) {
|
|
2188
|
+
await client.inappproducts.create(packageName, product);
|
|
2189
|
+
}
|
|
2190
|
+
return {
|
|
2191
|
+
created: toCreate.length,
|
|
2192
|
+
updated: toUpdate.length,
|
|
2193
|
+
unchanged: 0,
|
|
2194
|
+
skus,
|
|
2195
|
+
batchUsed,
|
|
2196
|
+
batchErrors
|
|
2197
|
+
};
|
|
1714
2198
|
}
|
|
1715
2199
|
|
|
1716
2200
|
// src/commands/purchases.ts
|
|
@@ -1934,7 +2418,7 @@ function parseGrantArg(grantStr) {
|
|
|
1934
2418
|
}
|
|
1935
2419
|
|
|
1936
2420
|
// src/commands/testers.ts
|
|
1937
|
-
import { readFile as
|
|
2421
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
1938
2422
|
async function listTesters(client, packageName, track) {
|
|
1939
2423
|
const edit = await client.edits.insert(packageName);
|
|
1940
2424
|
try {
|
|
@@ -1983,7 +2467,7 @@ async function removeTesters(client, packageName, track, groupEmails) {
|
|
|
1983
2467
|
}
|
|
1984
2468
|
}
|
|
1985
2469
|
async function importTestersFromCsv(client, packageName, track, csvPath) {
|
|
1986
|
-
const content = await
|
|
2470
|
+
const content = await readFile6(csvPath, "utf-8");
|
|
1987
2471
|
const emails = content.split(/[,\n\r]+/).map((e) => e.trim()).filter((e) => e.length > 0 && e.includes("@"));
|
|
1988
2472
|
if (emails.length === 0) {
|
|
1989
2473
|
throw new GpcError(
|
|
@@ -2122,7 +2606,7 @@ async function addRecoveryTargeting(client, packageName, actionId, targeting) {
|
|
|
2122
2606
|
}
|
|
2123
2607
|
|
|
2124
2608
|
// src/commands/data-safety.ts
|
|
2125
|
-
import { readFile as
|
|
2609
|
+
import { readFile as readFile7, writeFile as writeFile3 } from "fs/promises";
|
|
2126
2610
|
async function getDataSafety(client, packageName) {
|
|
2127
2611
|
const edit = await client.edits.insert(packageName);
|
|
2128
2612
|
try {
|
|
@@ -2150,16 +2634,21 @@ async function updateDataSafety(client, packageName, data) {
|
|
|
2150
2634
|
}
|
|
2151
2635
|
async function exportDataSafety(client, packageName, outputPath) {
|
|
2152
2636
|
const dataSafety = await getDataSafety(client, packageName);
|
|
2153
|
-
await
|
|
2637
|
+
await writeFile3(outputPath, JSON.stringify(dataSafety, null, 2) + "\n", "utf-8");
|
|
2154
2638
|
return dataSafety;
|
|
2155
2639
|
}
|
|
2156
2640
|
async function importDataSafety(client, packageName, filePath) {
|
|
2157
|
-
const content = await
|
|
2641
|
+
const content = await readFile7(filePath, "utf-8");
|
|
2158
2642
|
let data;
|
|
2159
2643
|
try {
|
|
2160
2644
|
data = JSON.parse(content);
|
|
2161
2645
|
} catch {
|
|
2162
|
-
throw new
|
|
2646
|
+
throw new GpcError(
|
|
2647
|
+
`Failed to parse data safety JSON from "${filePath}"`,
|
|
2648
|
+
"INVALID_JSON",
|
|
2649
|
+
1,
|
|
2650
|
+
"Ensure the file contains valid JSON matching the data safety schema."
|
|
2651
|
+
);
|
|
2163
2652
|
}
|
|
2164
2653
|
return updateDataSafety(client, packageName, data);
|
|
2165
2654
|
}
|
|
@@ -2348,6 +2837,78 @@ async function deleteOneTimeOffer(client, packageName, productId, offerId) {
|
|
|
2348
2837
|
}
|
|
2349
2838
|
}
|
|
2350
2839
|
|
|
2840
|
+
// src/utils/spinner.ts
|
|
2841
|
+
import process2 from "process";
|
|
2842
|
+
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
2843
|
+
var INTERVAL_MS = 80;
|
|
2844
|
+
function createSpinner(message) {
|
|
2845
|
+
const isTTY = process2.stderr.isTTY === true;
|
|
2846
|
+
let frameIndex = 0;
|
|
2847
|
+
let timer;
|
|
2848
|
+
let currentMessage = message;
|
|
2849
|
+
let started = false;
|
|
2850
|
+
function clearLine() {
|
|
2851
|
+
if (isTTY) {
|
|
2852
|
+
process2.stderr.write("\r\x1B[K");
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
function renderFrame() {
|
|
2856
|
+
const frame = FRAMES[frameIndex % FRAMES.length];
|
|
2857
|
+
process2.stderr.write(`\r\x1B[K${frame} ${currentMessage}`);
|
|
2858
|
+
frameIndex++;
|
|
2859
|
+
}
|
|
2860
|
+
return {
|
|
2861
|
+
start() {
|
|
2862
|
+
if (started) return;
|
|
2863
|
+
started = true;
|
|
2864
|
+
if (!isTTY) {
|
|
2865
|
+
process2.stderr.write(`${currentMessage}
|
|
2866
|
+
`);
|
|
2867
|
+
return;
|
|
2868
|
+
}
|
|
2869
|
+
renderFrame();
|
|
2870
|
+
timer = setInterval(renderFrame, INTERVAL_MS);
|
|
2871
|
+
},
|
|
2872
|
+
stop(msg) {
|
|
2873
|
+
if (timer) {
|
|
2874
|
+
clearInterval(timer);
|
|
2875
|
+
timer = void 0;
|
|
2876
|
+
}
|
|
2877
|
+
const text = msg ?? currentMessage;
|
|
2878
|
+
if (isTTY) {
|
|
2879
|
+
clearLine();
|
|
2880
|
+
process2.stderr.write(`\u2714 ${text}
|
|
2881
|
+
`);
|
|
2882
|
+
} else if (!started) {
|
|
2883
|
+
process2.stderr.write(`${text}
|
|
2884
|
+
`);
|
|
2885
|
+
}
|
|
2886
|
+
started = false;
|
|
2887
|
+
},
|
|
2888
|
+
fail(msg) {
|
|
2889
|
+
if (timer) {
|
|
2890
|
+
clearInterval(timer);
|
|
2891
|
+
timer = void 0;
|
|
2892
|
+
}
|
|
2893
|
+
const text = msg ?? currentMessage;
|
|
2894
|
+
if (isTTY) {
|
|
2895
|
+
clearLine();
|
|
2896
|
+
process2.stderr.write(`\u2718 ${text}
|
|
2897
|
+
`);
|
|
2898
|
+
} else if (!started) {
|
|
2899
|
+
process2.stderr.write(`${text}
|
|
2900
|
+
`);
|
|
2901
|
+
}
|
|
2902
|
+
started = false;
|
|
2903
|
+
},
|
|
2904
|
+
update(msg) {
|
|
2905
|
+
currentMessage = msg;
|
|
2906
|
+
if (!isTTY || !started) return;
|
|
2907
|
+
renderFrame();
|
|
2908
|
+
}
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2351
2912
|
// src/utils/safe-path.ts
|
|
2352
2913
|
import { resolve, normalize } from "path";
|
|
2353
2914
|
function safePath(userPath) {
|
|
@@ -2408,16 +2969,16 @@ function sortResults(items, sortSpec) {
|
|
|
2408
2969
|
}
|
|
2409
2970
|
|
|
2410
2971
|
// src/commands/plugin-scaffold.ts
|
|
2411
|
-
import { mkdir as
|
|
2412
|
-
import { join as
|
|
2972
|
+
import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
|
|
2973
|
+
import { join as join5 } from "path";
|
|
2413
2974
|
async function scaffoldPlugin(options) {
|
|
2414
2975
|
const { name, dir, description = `GPC plugin: ${name}` } = options;
|
|
2415
2976
|
const pluginName = name.startsWith("gpc-plugin-") ? name : `gpc-plugin-${name}`;
|
|
2416
2977
|
const shortName = pluginName.replace(/^gpc-plugin-/, "");
|
|
2417
|
-
const srcDir =
|
|
2418
|
-
const testDir =
|
|
2419
|
-
await
|
|
2420
|
-
await
|
|
2978
|
+
const srcDir = join5(dir, "src");
|
|
2979
|
+
const testDir = join5(dir, "tests");
|
|
2980
|
+
await mkdir3(srcDir, { recursive: true });
|
|
2981
|
+
await mkdir3(testDir, { recursive: true });
|
|
2421
2982
|
const files = [];
|
|
2422
2983
|
const pkg = {
|
|
2423
2984
|
name: pluginName,
|
|
@@ -2451,7 +3012,7 @@ async function scaffoldPlugin(options) {
|
|
|
2451
3012
|
vitest: "^3.0.0"
|
|
2452
3013
|
}
|
|
2453
3014
|
};
|
|
2454
|
-
await
|
|
3015
|
+
await writeFile4(join5(dir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
|
|
2455
3016
|
files.push("package.json");
|
|
2456
3017
|
const tsconfig = {
|
|
2457
3018
|
compilerOptions: {
|
|
@@ -2467,7 +3028,7 @@ async function scaffoldPlugin(options) {
|
|
|
2467
3028
|
},
|
|
2468
3029
|
include: ["src"]
|
|
2469
3030
|
};
|
|
2470
|
-
await
|
|
3031
|
+
await writeFile4(join5(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
|
|
2471
3032
|
files.push("tsconfig.json");
|
|
2472
3033
|
const srcContent = `import { definePlugin } from "@gpc-cli/plugin-sdk";
|
|
2473
3034
|
import type { CommandEvent, CommandResult } from "@gpc-cli/plugin-sdk";
|
|
@@ -2500,7 +3061,7 @@ export const plugin = definePlugin({
|
|
|
2500
3061
|
},
|
|
2501
3062
|
});
|
|
2502
3063
|
`;
|
|
2503
|
-
await
|
|
3064
|
+
await writeFile4(join5(srcDir, "index.ts"), srcContent);
|
|
2504
3065
|
files.push("src/index.ts");
|
|
2505
3066
|
const testContent = `import { describe, it, expect, vi } from "vitest";
|
|
2506
3067
|
import { plugin } from "../src/index";
|
|
@@ -2525,14 +3086,14 @@ describe("${pluginName}", () => {
|
|
|
2525
3086
|
});
|
|
2526
3087
|
});
|
|
2527
3088
|
`;
|
|
2528
|
-
await
|
|
3089
|
+
await writeFile4(join5(testDir, "plugin.test.ts"), testContent);
|
|
2529
3090
|
files.push("tests/plugin.test.ts");
|
|
2530
3091
|
return { dir, files };
|
|
2531
3092
|
}
|
|
2532
3093
|
|
|
2533
3094
|
// src/audit.ts
|
|
2534
|
-
import { appendFile, chmod, mkdir as
|
|
2535
|
-
import { join as
|
|
3095
|
+
import { appendFile, chmod, mkdir as mkdir4 } from "fs/promises";
|
|
3096
|
+
import { join as join6 } from "path";
|
|
2536
3097
|
var auditDir = null;
|
|
2537
3098
|
function initAudit(configDir) {
|
|
2538
3099
|
auditDir = configDir;
|
|
@@ -2540,8 +3101,8 @@ function initAudit(configDir) {
|
|
|
2540
3101
|
async function writeAuditLog(entry) {
|
|
2541
3102
|
if (!auditDir) return;
|
|
2542
3103
|
try {
|
|
2543
|
-
await
|
|
2544
|
-
const logPath =
|
|
3104
|
+
await mkdir4(auditDir, { recursive: true, mode: 448 });
|
|
3105
|
+
const logPath = join6(auditDir, "audit.log");
|
|
2545
3106
|
const redactedEntry = redactAuditArgs(entry);
|
|
2546
3107
|
const line = JSON.stringify(redactedEntry) + "\n";
|
|
2547
3108
|
await appendFile(logPath, line, { encoding: "utf-8", mode: 384 });
|
|
@@ -2724,7 +3285,7 @@ function detectFileType(filePath) {
|
|
|
2724
3285
|
}
|
|
2725
3286
|
|
|
2726
3287
|
// src/commands/generated-apks.ts
|
|
2727
|
-
import { writeFile as
|
|
3288
|
+
import { writeFile as writeFile5 } from "fs/promises";
|
|
2728
3289
|
async function listGeneratedApks(client, packageName, versionCode) {
|
|
2729
3290
|
if (!Number.isInteger(versionCode) || versionCode <= 0) {
|
|
2730
3291
|
throw new GpcError(
|
|
@@ -2755,9 +3316,71 @@ async function downloadGeneratedApk(client, packageName, versionCode, apkId, out
|
|
|
2755
3316
|
}
|
|
2756
3317
|
const buffer = await client.generatedApks.download(packageName, versionCode, apkId);
|
|
2757
3318
|
const bytes = new Uint8Array(buffer);
|
|
2758
|
-
await
|
|
3319
|
+
await writeFile5(outputPath, bytes);
|
|
2759
3320
|
return { path: outputPath, sizeBytes: bytes.byteLength };
|
|
2760
3321
|
}
|
|
3322
|
+
|
|
3323
|
+
// src/commands/purchase-options.ts
|
|
3324
|
+
async function listPurchaseOptions(client, packageName) {
|
|
3325
|
+
try {
|
|
3326
|
+
return await client.purchaseOptions.list(packageName);
|
|
3327
|
+
} catch (error) {
|
|
3328
|
+
throw new GpcError(
|
|
3329
|
+
`Failed to list purchase options: ${error instanceof Error ? error.message : String(error)}`,
|
|
3330
|
+
"PURCHASE_OPTIONS_LIST_FAILED",
|
|
3331
|
+
4,
|
|
3332
|
+
"Check your package name and API permissions."
|
|
3333
|
+
);
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
async function getPurchaseOption(client, packageName, purchaseOptionId) {
|
|
3337
|
+
try {
|
|
3338
|
+
return await client.purchaseOptions.get(packageName, purchaseOptionId);
|
|
3339
|
+
} catch (error) {
|
|
3340
|
+
throw new GpcError(
|
|
3341
|
+
`Failed to get purchase option "${purchaseOptionId}": ${error instanceof Error ? error.message : String(error)}`,
|
|
3342
|
+
"PURCHASE_OPTION_GET_FAILED",
|
|
3343
|
+
4,
|
|
3344
|
+
"Check that the purchase option ID exists."
|
|
3345
|
+
);
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
async function createPurchaseOption(client, packageName, data) {
|
|
3349
|
+
try {
|
|
3350
|
+
return await client.purchaseOptions.create(packageName, data);
|
|
3351
|
+
} catch (error) {
|
|
3352
|
+
throw new GpcError(
|
|
3353
|
+
`Failed to create purchase option: ${error instanceof Error ? error.message : String(error)}`,
|
|
3354
|
+
"PURCHASE_OPTION_CREATE_FAILED",
|
|
3355
|
+
4,
|
|
3356
|
+
"Check your purchase option data and API permissions."
|
|
3357
|
+
);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
async function activatePurchaseOption(client, packageName, purchaseOptionId) {
|
|
3361
|
+
try {
|
|
3362
|
+
return await client.purchaseOptions.activate(packageName, purchaseOptionId);
|
|
3363
|
+
} catch (error) {
|
|
3364
|
+
throw new GpcError(
|
|
3365
|
+
`Failed to activate purchase option "${purchaseOptionId}": ${error instanceof Error ? error.message : String(error)}`,
|
|
3366
|
+
"PURCHASE_OPTION_ACTIVATE_FAILED",
|
|
3367
|
+
4,
|
|
3368
|
+
"Check that the purchase option exists and is in a valid state for activation."
|
|
3369
|
+
);
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
async function deactivatePurchaseOption(client, packageName, purchaseOptionId) {
|
|
3373
|
+
try {
|
|
3374
|
+
return await client.purchaseOptions.deactivate(packageName, purchaseOptionId);
|
|
3375
|
+
} catch (error) {
|
|
3376
|
+
throw new GpcError(
|
|
3377
|
+
`Failed to deactivate purchase option "${purchaseOptionId}": ${error instanceof Error ? error.message : String(error)}`,
|
|
3378
|
+
"PURCHASE_OPTION_DEACTIVATE_FAILED",
|
|
3379
|
+
4,
|
|
3380
|
+
"Check that the purchase option exists and is in a valid state for deactivation."
|
|
3381
|
+
);
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
2761
3384
|
export {
|
|
2762
3385
|
ApiError,
|
|
2763
3386
|
ConfigError,
|
|
@@ -2769,8 +3392,10 @@ export {
|
|
|
2769
3392
|
acknowledgeProductPurchase,
|
|
2770
3393
|
activateBasePlan,
|
|
2771
3394
|
activateOffer,
|
|
3395
|
+
activatePurchaseOption,
|
|
2772
3396
|
addRecoveryTargeting,
|
|
2773
3397
|
addTesters,
|
|
3398
|
+
batchSyncInAppProducts,
|
|
2774
3399
|
cancelRecoveryAction,
|
|
2775
3400
|
cancelSubscriptionPurchase,
|
|
2776
3401
|
checkThreshold,
|
|
@@ -2784,10 +3409,14 @@ export {
|
|
|
2784
3409
|
createOffer,
|
|
2785
3410
|
createOneTimeOffer,
|
|
2786
3411
|
createOneTimeProduct,
|
|
3412
|
+
createPurchaseOption,
|
|
2787
3413
|
createRecoveryAction,
|
|
3414
|
+
createSpinner,
|
|
2788
3415
|
createSubscription,
|
|
3416
|
+
createTrack,
|
|
2789
3417
|
deactivateBasePlan,
|
|
2790
3418
|
deactivateOffer,
|
|
3419
|
+
deactivatePurchaseOption,
|
|
2791
3420
|
deferSubscriptionPurchase,
|
|
2792
3421
|
deleteBasePlan,
|
|
2793
3422
|
deleteImage,
|
|
@@ -2798,6 +3427,7 @@ export {
|
|
|
2798
3427
|
deleteOneTimeProduct,
|
|
2799
3428
|
deleteSubscription,
|
|
2800
3429
|
deployRecoveryAction,
|
|
3430
|
+
detectFastlane,
|
|
2801
3431
|
detectOutputFormat,
|
|
2802
3432
|
diffListings,
|
|
2803
3433
|
diffListingsCommand,
|
|
@@ -2805,11 +3435,14 @@ export {
|
|
|
2805
3435
|
downloadGeneratedApk,
|
|
2806
3436
|
downloadReport,
|
|
2807
3437
|
exportDataSafety,
|
|
3438
|
+
exportImages,
|
|
2808
3439
|
exportReviews,
|
|
2809
3440
|
formatCustomPayload,
|
|
2810
3441
|
formatDiscordPayload,
|
|
3442
|
+
formatJunit,
|
|
2811
3443
|
formatOutput,
|
|
2812
3444
|
formatSlackPayload,
|
|
3445
|
+
generateMigrationPlan,
|
|
2813
3446
|
generateNotesFromGit,
|
|
2814
3447
|
getAppInfo,
|
|
2815
3448
|
getCountryAvailability,
|
|
@@ -2822,6 +3455,7 @@ export {
|
|
|
2822
3455
|
getOneTimeOffer,
|
|
2823
3456
|
getOneTimeProduct,
|
|
2824
3457
|
getProductPurchase,
|
|
3458
|
+
getPurchaseOption,
|
|
2825
3459
|
getReleasesStatus,
|
|
2826
3460
|
getReview,
|
|
2827
3461
|
getSubscription,
|
|
@@ -2851,6 +3485,7 @@ export {
|
|
|
2851
3485
|
listOffers,
|
|
2852
3486
|
listOneTimeOffers,
|
|
2853
3487
|
listOneTimeProducts,
|
|
3488
|
+
listPurchaseOptions,
|
|
2854
3489
|
listRecoveryActions,
|
|
2855
3490
|
listReports,
|
|
2856
3491
|
listReviews,
|
|
@@ -2860,6 +3495,8 @@ export {
|
|
|
2860
3495
|
listUsers,
|
|
2861
3496
|
listVoidedPurchases,
|
|
2862
3497
|
migratePrices,
|
|
3498
|
+
parseAppfile,
|
|
3499
|
+
parseFastfile,
|
|
2863
3500
|
parseGrantArg,
|
|
2864
3501
|
parseMonth,
|
|
2865
3502
|
promoteRelease,
|
|
@@ -2891,7 +3528,9 @@ export {
|
|
|
2891
3528
|
updateOneTimeProduct,
|
|
2892
3529
|
updateRollout,
|
|
2893
3530
|
updateSubscription,
|
|
3531
|
+
updateTrackConfig,
|
|
2894
3532
|
updateUser,
|
|
3533
|
+
uploadExternallyHosted,
|
|
2895
3534
|
uploadImage,
|
|
2896
3535
|
uploadInternalSharing,
|
|
2897
3536
|
uploadRelease,
|
|
@@ -2900,6 +3539,7 @@ export {
|
|
|
2900
3539
|
validateReleaseNotes,
|
|
2901
3540
|
validateUploadFile,
|
|
2902
3541
|
writeAuditLog,
|
|
2903
|
-
writeListingsToDir
|
|
3542
|
+
writeListingsToDir,
|
|
3543
|
+
writeMigrationOutput
|
|
2904
3544
|
};
|
|
2905
3545
|
//# sourceMappingURL=index.js.map
|