@gpc-cli/core 0.9.42 → 0.9.44
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 +21 -17
- package/dist/index.js +351 -192
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -363,10 +363,7 @@ function formatJunit(data, commandName = "command") {
|
|
|
363
363
|
}
|
|
364
364
|
|
|
365
365
|
// src/plugins.ts
|
|
366
|
-
var FIRST_PARTY_PLUGINS = /* @__PURE__ */ new Set([
|
|
367
|
-
"@gpc-cli/plugin-ci",
|
|
368
|
-
"@gpc-cli/plugin-sdk"
|
|
369
|
-
]);
|
|
366
|
+
var FIRST_PARTY_PLUGINS = /* @__PURE__ */ new Set(["@gpc-cli/plugin-ci", "@gpc-cli/plugin-sdk"]);
|
|
370
367
|
var PluginManager = class {
|
|
371
368
|
plugins = [];
|
|
372
369
|
beforeHandlers = [];
|
|
@@ -755,7 +752,13 @@ ${validation.errors.join("\n")}`,
|
|
|
755
752
|
if (options.onUploadProgress) options.onUploadProgress(event);
|
|
756
753
|
}
|
|
757
754
|
};
|
|
758
|
-
const bundle = isApk ? await client.apks.upload(packageName, edit.id, filePath, uploadOpts) : await client.bundles.upload(
|
|
755
|
+
const bundle = isApk ? await client.apks.upload(packageName, edit.id, filePath, uploadOpts) : await client.bundles.upload(
|
|
756
|
+
packageName,
|
|
757
|
+
edit.id,
|
|
758
|
+
filePath,
|
|
759
|
+
uploadOpts,
|
|
760
|
+
options.deviceTierConfigId
|
|
761
|
+
);
|
|
759
762
|
if (options.mappingFile) {
|
|
760
763
|
await client.deobfuscation.upload(
|
|
761
764
|
packageName,
|
|
@@ -773,7 +776,9 @@ ${validation.errors.join("\n")}`,
|
|
|
773
776
|
...options.releaseName && { name: options.releaseName }
|
|
774
777
|
};
|
|
775
778
|
await client.tracks.update(packageName, edit.id, options.track, release);
|
|
776
|
-
|
|
779
|
+
if (!options.commitOptions?.changesNotSentForReview) {
|
|
780
|
+
await client.edits.validate(packageName, edit.id);
|
|
781
|
+
}
|
|
777
782
|
await client.edits.commit(packageName, edit.id, options.commitOptions);
|
|
778
783
|
return {
|
|
779
784
|
versionCode: bundle.versionCode,
|
|
@@ -839,7 +844,9 @@ async function promoteRelease(client, packageName, fromTrack, toTrack, options)
|
|
|
839
844
|
releaseNotes: options?.releaseNotes || currentRelease.releaseNotes || []
|
|
840
845
|
};
|
|
841
846
|
await client.tracks.update(packageName, edit.id, toTrack, release);
|
|
842
|
-
|
|
847
|
+
if (!options?.commitOptions?.changesNotSentForReview) {
|
|
848
|
+
await client.edits.validate(packageName, edit.id);
|
|
849
|
+
}
|
|
843
850
|
await client.edits.commit(packageName, edit.id, options?.commitOptions);
|
|
844
851
|
return {
|
|
845
852
|
track: toTrack,
|
|
@@ -906,7 +913,9 @@ async function updateRollout(client, packageName, track, action, userFraction, c
|
|
|
906
913
|
releaseNotes: currentRelease.releaseNotes || []
|
|
907
914
|
};
|
|
908
915
|
await client.tracks.update(packageName, edit.id, track, release);
|
|
909
|
-
|
|
916
|
+
if (!commitOptions?.changesNotSentForReview) {
|
|
917
|
+
await client.edits.validate(packageName, edit.id);
|
|
918
|
+
}
|
|
910
919
|
await client.edits.commit(packageName, edit.id, commitOptions);
|
|
911
920
|
return {
|
|
912
921
|
track,
|
|
@@ -944,7 +953,9 @@ async function createTrack(client, packageName, trackName, commitOptions) {
|
|
|
944
953
|
const edit = await client.edits.insert(packageName);
|
|
945
954
|
try {
|
|
946
955
|
const track = await client.tracks.create(packageName, edit.id, trackName);
|
|
947
|
-
|
|
956
|
+
if (!commitOptions?.changesNotSentForReview) {
|
|
957
|
+
await client.edits.validate(packageName, edit.id);
|
|
958
|
+
}
|
|
948
959
|
await client.edits.commit(packageName, edit.id, commitOptions);
|
|
949
960
|
return track;
|
|
950
961
|
} catch (error) {
|
|
@@ -978,7 +989,9 @@ async function updateTrackConfig(client, packageName, trackName, config, commitO
|
|
|
978
989
|
release.name = config["name"];
|
|
979
990
|
}
|
|
980
991
|
const track = await client.tracks.update(packageName, edit.id, trackName, release);
|
|
981
|
-
|
|
992
|
+
if (!commitOptions?.changesNotSentForReview) {
|
|
993
|
+
await client.edits.validate(packageName, edit.id);
|
|
994
|
+
}
|
|
982
995
|
await client.edits.commit(packageName, edit.id, commitOptions);
|
|
983
996
|
return track;
|
|
984
997
|
} catch (error) {
|
|
@@ -1052,7 +1065,9 @@ async function uploadExternallyHosted(client, packageName, data, commitOptions)
|
|
|
1052
1065
|
const edit = await client.edits.insert(packageName);
|
|
1053
1066
|
try {
|
|
1054
1067
|
const result = await client.apks.addExternallyHosted(packageName, edit.id, data);
|
|
1055
|
-
|
|
1068
|
+
if (!commitOptions?.changesNotSentForReview) {
|
|
1069
|
+
await client.edits.validate(packageName, edit.id);
|
|
1070
|
+
}
|
|
1056
1071
|
await client.edits.commit(packageName, edit.id, commitOptions);
|
|
1057
1072
|
return result;
|
|
1058
1073
|
} catch (error) {
|
|
@@ -1403,7 +1418,9 @@ async function updateListing(client, packageName, language, data, commitOptions)
|
|
|
1403
1418
|
const edit = await client.edits.insert(packageName);
|
|
1404
1419
|
try {
|
|
1405
1420
|
const listing = await client.listings.patch(packageName, edit.id, language, data);
|
|
1406
|
-
|
|
1421
|
+
if (!commitOptions?.changesNotSentForReview) {
|
|
1422
|
+
await client.edits.validate(packageName, edit.id);
|
|
1423
|
+
}
|
|
1407
1424
|
await client.edits.commit(packageName, edit.id, commitOptions);
|
|
1408
1425
|
return listing;
|
|
1409
1426
|
} catch (error) {
|
|
@@ -1417,7 +1434,9 @@ async function deleteListing(client, packageName, language, commitOptions) {
|
|
|
1417
1434
|
const edit = await client.edits.insert(packageName);
|
|
1418
1435
|
try {
|
|
1419
1436
|
await client.listings.delete(packageName, edit.id, language);
|
|
1420
|
-
|
|
1437
|
+
if (!commitOptions?.changesNotSentForReview) {
|
|
1438
|
+
await client.edits.validate(packageName, edit.id);
|
|
1439
|
+
}
|
|
1421
1440
|
await client.edits.commit(packageName, edit.id, commitOptions);
|
|
1422
1441
|
} catch (error) {
|
|
1423
1442
|
await client.edits.delete(packageName, edit.id).catch(() => {
|
|
@@ -1541,7 +1560,9 @@ ${details}`,
|
|
|
1541
1560
|
const { language, ...data } = listing;
|
|
1542
1561
|
await client.listings.update(packageName, edit.id, language, data);
|
|
1543
1562
|
}
|
|
1544
|
-
|
|
1563
|
+
if (!options?.commitOptions?.changesNotSentForReview) {
|
|
1564
|
+
await client.edits.validate(packageName, edit.id);
|
|
1565
|
+
}
|
|
1545
1566
|
await client.edits.commit(packageName, edit.id, options?.commitOptions);
|
|
1546
1567
|
return {
|
|
1547
1568
|
updated: localListings.length,
|
|
@@ -1583,7 +1604,9 @@ async function uploadImage(client, packageName, language, imageType, filePath, c
|
|
|
1583
1604
|
const edit = await client.edits.insert(packageName);
|
|
1584
1605
|
try {
|
|
1585
1606
|
const image = await client.images.upload(packageName, edit.id, language, imageType, filePath);
|
|
1586
|
-
|
|
1607
|
+
if (!commitOptions?.changesNotSentForReview) {
|
|
1608
|
+
await client.edits.validate(packageName, edit.id);
|
|
1609
|
+
}
|
|
1587
1610
|
await client.edits.commit(packageName, edit.id, commitOptions);
|
|
1588
1611
|
return image;
|
|
1589
1612
|
} catch (error) {
|
|
@@ -1597,7 +1620,9 @@ async function deleteImage(client, packageName, language, imageType, imageId, co
|
|
|
1597
1620
|
const edit = await client.edits.insert(packageName);
|
|
1598
1621
|
try {
|
|
1599
1622
|
await client.images.delete(packageName, edit.id, language, imageType, imageId);
|
|
1600
|
-
|
|
1623
|
+
if (!commitOptions?.changesNotSentForReview) {
|
|
1624
|
+
await client.edits.validate(packageName, edit.id);
|
|
1625
|
+
}
|
|
1601
1626
|
await client.edits.commit(packageName, edit.id, commitOptions);
|
|
1602
1627
|
} catch (error) {
|
|
1603
1628
|
await client.edits.delete(packageName, edit.id).catch(() => {
|
|
@@ -1711,7 +1736,9 @@ async function updateAppDetails(client, packageName, details, commitOptions) {
|
|
|
1711
1736
|
const edit = await client.edits.insert(packageName);
|
|
1712
1737
|
try {
|
|
1713
1738
|
const result = await client.details.patch(packageName, edit.id, details);
|
|
1714
|
-
|
|
1739
|
+
if (!commitOptions?.changesNotSentForReview) {
|
|
1740
|
+
await client.edits.validate(packageName, edit.id);
|
|
1741
|
+
}
|
|
1715
1742
|
await client.edits.commit(packageName, edit.id, commitOptions);
|
|
1716
1743
|
return result;
|
|
1717
1744
|
} catch (error) {
|
|
@@ -2428,16 +2455,20 @@ function analyzeReviews(reviews) {
|
|
|
2428
2455
|
async function listReviews(client, packageName, options) {
|
|
2429
2456
|
let reviews;
|
|
2430
2457
|
if (options?.all) {
|
|
2431
|
-
const { items } = await paginateAll(
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2458
|
+
const { items } = await paginateAll(
|
|
2459
|
+
async (pageToken) => {
|
|
2460
|
+
const apiOptions = { token: pageToken };
|
|
2461
|
+
if (options?.translationLanguage)
|
|
2462
|
+
apiOptions.translationLanguage = options.translationLanguage;
|
|
2463
|
+
if (options?.maxResults) apiOptions.maxResults = options.maxResults;
|
|
2464
|
+
const response = await client.reviews.list(packageName, apiOptions);
|
|
2465
|
+
return {
|
|
2466
|
+
items: response.reviews || [],
|
|
2467
|
+
nextPageToken: response.tokenPagination?.nextPageToken
|
|
2468
|
+
};
|
|
2469
|
+
},
|
|
2470
|
+
{ limit: options?.limit }
|
|
2471
|
+
);
|
|
2441
2472
|
reviews = items;
|
|
2442
2473
|
} else {
|
|
2443
2474
|
const apiOptions = {};
|
|
@@ -3312,10 +3343,6 @@ async function revokeSubscriptionPurchase(client, packageName, token) {
|
|
|
3312
3343
|
validatePackageName(packageName);
|
|
3313
3344
|
return client.purchases.revokeSubscriptionV2(packageName, token);
|
|
3314
3345
|
}
|
|
3315
|
-
async function refundSubscriptionV2(client, packageName, token) {
|
|
3316
|
-
validatePackageName(packageName);
|
|
3317
|
-
return client.purchases.refundSubscriptionV2(packageName, token);
|
|
3318
|
-
}
|
|
3319
3346
|
async function listVoidedPurchases(client, packageName, options) {
|
|
3320
3347
|
validatePackageName(packageName);
|
|
3321
3348
|
if (options?.limit || options?.nextPage) {
|
|
@@ -3351,10 +3378,20 @@ async function getOrderDetails(client, packageName, orderId) {
|
|
|
3351
3378
|
async function batchGetOrders(client, packageName, orderIds) {
|
|
3352
3379
|
validatePackageName(packageName);
|
|
3353
3380
|
if (orderIds.length === 0) {
|
|
3354
|
-
throw new GpcError(
|
|
3381
|
+
throw new GpcError(
|
|
3382
|
+
"No order IDs provided",
|
|
3383
|
+
"ORDERS_BATCH_EMPTY",
|
|
3384
|
+
2,
|
|
3385
|
+
"Pass at least one order ID with --ids"
|
|
3386
|
+
);
|
|
3355
3387
|
}
|
|
3356
3388
|
if (orderIds.length > 1e3) {
|
|
3357
|
-
throw new GpcError(
|
|
3389
|
+
throw new GpcError(
|
|
3390
|
+
`Too many order IDs (${orderIds.length}). Maximum is 1000.`,
|
|
3391
|
+
"ORDERS_BATCH_LIMIT",
|
|
3392
|
+
2,
|
|
3393
|
+
"Split into multiple requests of 1000 or fewer"
|
|
3394
|
+
);
|
|
3358
3395
|
}
|
|
3359
3396
|
return client.orders.batchGet(packageName, orderIds);
|
|
3360
3397
|
}
|
|
@@ -3480,8 +3517,22 @@ async function listUsers(client, developerId, options) {
|
|
|
3480
3517
|
const response = await client.list(developerId, options);
|
|
3481
3518
|
return { users: response.users || [], nextPageToken: response.nextPageToken };
|
|
3482
3519
|
}
|
|
3483
|
-
async function getUser(client, developerId,
|
|
3484
|
-
|
|
3520
|
+
async function getUser(client, developerId, email) {
|
|
3521
|
+
const lowerEmail = email.toLowerCase();
|
|
3522
|
+
let pageToken;
|
|
3523
|
+
do {
|
|
3524
|
+
const response = await client.list(developerId, pageToken ? { pageToken } : void 0);
|
|
3525
|
+
const users = response.users || [];
|
|
3526
|
+
const match = users.find((u) => u.email?.toLowerCase() === lowerEmail);
|
|
3527
|
+
if (match) return match;
|
|
3528
|
+
pageToken = response.nextPageToken;
|
|
3529
|
+
} while (pageToken);
|
|
3530
|
+
throw new GpcError(
|
|
3531
|
+
`User "${email}" not found in developer account.`,
|
|
3532
|
+
"USER_NOT_FOUND",
|
|
3533
|
+
4,
|
|
3534
|
+
"Check the email address and ensure the user has access to this developer account."
|
|
3535
|
+
);
|
|
3485
3536
|
}
|
|
3486
3537
|
async function inviteUser(client, developerId, email, permissions, grants) {
|
|
3487
3538
|
const user = { email };
|
|
@@ -3569,7 +3620,9 @@ async function addTesters(client, packageName, track, groupEmails, commitOptions
|
|
|
3569
3620
|
const updated = await client.testers.update(packageName, edit.id, track, {
|
|
3570
3621
|
googleGroups: [...existing]
|
|
3571
3622
|
});
|
|
3572
|
-
|
|
3623
|
+
if (!commitOptions?.changesNotSentForReview) {
|
|
3624
|
+
await client.edits.validate(packageName, edit.id);
|
|
3625
|
+
}
|
|
3573
3626
|
await client.edits.commit(packageName, edit.id, commitOptions);
|
|
3574
3627
|
return updated;
|
|
3575
3628
|
} catch (error) {
|
|
@@ -3587,7 +3640,9 @@ async function removeTesters(client, packageName, track, groupEmails, commitOpti
|
|
|
3587
3640
|
const updated = await client.testers.update(packageName, edit.id, track, {
|
|
3588
3641
|
googleGroups: filtered
|
|
3589
3642
|
});
|
|
3590
|
-
|
|
3643
|
+
if (!commitOptions?.changesNotSentForReview) {
|
|
3644
|
+
await client.edits.validate(packageName, edit.id);
|
|
3645
|
+
}
|
|
3591
3646
|
await client.edits.commit(packageName, edit.id, commitOptions);
|
|
3592
3647
|
return updated;
|
|
3593
3648
|
} catch (error) {
|
|
@@ -3596,7 +3651,7 @@ async function removeTesters(client, packageName, track, groupEmails, commitOpti
|
|
|
3596
3651
|
throw error;
|
|
3597
3652
|
}
|
|
3598
3653
|
}
|
|
3599
|
-
async function importTestersFromCsv(client, packageName, track, csvPath) {
|
|
3654
|
+
async function importTestersFromCsv(client, packageName, track, csvPath, commitOptions) {
|
|
3600
3655
|
const content = await readFile5(csvPath, "utf-8");
|
|
3601
3656
|
const emails = content.split(/[,\n\r]+/).map((e) => e.trim()).filter((e) => e.length > 0 && e.includes("@"));
|
|
3602
3657
|
if (emails.length === 0) {
|
|
@@ -3607,7 +3662,7 @@ async function importTestersFromCsv(client, packageName, track, csvPath) {
|
|
|
3607
3662
|
"The CSV file must contain email addresses separated by commas or newlines. Each email must contain an @ symbol."
|
|
3608
3663
|
);
|
|
3609
3664
|
}
|
|
3610
|
-
const testers = await addTesters(client, packageName, track, emails);
|
|
3665
|
+
const testers = await addTesters(client, packageName, track, emails, commitOptions);
|
|
3611
3666
|
return { added: emails.length, testers };
|
|
3612
3667
|
}
|
|
3613
3668
|
|
|
@@ -3871,7 +3926,7 @@ var OTP_ID_FIELDS = /* @__PURE__ */ new Set(["productId", "packageName"]);
|
|
|
3871
3926
|
function deriveOtpUpdateMask(data) {
|
|
3872
3927
|
return Object.keys(data).filter((k) => !OTP_ID_FIELDS.has(k)).join(",");
|
|
3873
3928
|
}
|
|
3874
|
-
var OTP_OFFER_ID_FIELDS = /* @__PURE__ */ new Set(["productId", "offerId"]);
|
|
3929
|
+
var OTP_OFFER_ID_FIELDS = /* @__PURE__ */ new Set(["packageName", "productId", "purchaseOptionId", "offerId"]);
|
|
3875
3930
|
function deriveOtpOfferUpdateMask(data) {
|
|
3876
3931
|
return Object.keys(data).filter((k) => !OTP_OFFER_ID_FIELDS.has(k)).join(",");
|
|
3877
3932
|
}
|
|
@@ -3900,9 +3955,9 @@ async function deleteOneTimeProduct(client, packageName, productId) {
|
|
|
3900
3955
|
);
|
|
3901
3956
|
}
|
|
3902
3957
|
}
|
|
3903
|
-
async function listOneTimeOffers(client, packageName, productId) {
|
|
3958
|
+
async function listOneTimeOffers(client, packageName, productId, purchaseOptionId = "-") {
|
|
3904
3959
|
try {
|
|
3905
|
-
return await client.oneTimeProducts.listOffers(packageName, productId);
|
|
3960
|
+
return await client.oneTimeProducts.listOffers(packageName, productId, purchaseOptionId);
|
|
3906
3961
|
} catch (error) {
|
|
3907
3962
|
throw new GpcError(
|
|
3908
3963
|
`Failed to list offers for product "${productId}": ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -3912,9 +3967,9 @@ async function listOneTimeOffers(client, packageName, productId) {
|
|
|
3912
3967
|
);
|
|
3913
3968
|
}
|
|
3914
3969
|
}
|
|
3915
|
-
async function getOneTimeOffer(client, packageName, productId, offerId) {
|
|
3970
|
+
async function getOneTimeOffer(client, packageName, productId, offerId, purchaseOptionId = "-") {
|
|
3916
3971
|
try {
|
|
3917
|
-
return await client.oneTimeProducts.getOffer(packageName, productId, offerId);
|
|
3972
|
+
return await client.oneTimeProducts.getOffer(packageName, productId, purchaseOptionId, offerId);
|
|
3918
3973
|
} catch (error) {
|
|
3919
3974
|
throw new GpcError(
|
|
3920
3975
|
`Failed to get offer "${offerId}" for product "${productId}": ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -3924,9 +3979,9 @@ async function getOneTimeOffer(client, packageName, productId, offerId) {
|
|
|
3924
3979
|
);
|
|
3925
3980
|
}
|
|
3926
3981
|
}
|
|
3927
|
-
async function createOneTimeOffer(client, packageName, productId, data) {
|
|
3982
|
+
async function createOneTimeOffer(client, packageName, productId, data, purchaseOptionId = "-") {
|
|
3928
3983
|
try {
|
|
3929
|
-
return await client.oneTimeProducts.createOffer(packageName, productId, data);
|
|
3984
|
+
return await client.oneTimeProducts.createOffer(packageName, productId, purchaseOptionId, data);
|
|
3930
3985
|
} catch (error) {
|
|
3931
3986
|
throw new GpcError(
|
|
3932
3987
|
`Failed to create offer for product "${productId}": ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -3936,10 +3991,17 @@ async function createOneTimeOffer(client, packageName, productId, data) {
|
|
|
3936
3991
|
);
|
|
3937
3992
|
}
|
|
3938
3993
|
}
|
|
3939
|
-
async function updateOneTimeOffer(client, packageName, productId, offerId, data, updateMask) {
|
|
3994
|
+
async function updateOneTimeOffer(client, packageName, productId, offerId, data, updateMask, purchaseOptionId = "-") {
|
|
3940
3995
|
try {
|
|
3941
3996
|
const mask = updateMask || deriveOtpOfferUpdateMask(data);
|
|
3942
|
-
return await client.oneTimeProducts.updateOffer(
|
|
3997
|
+
return await client.oneTimeProducts.updateOffer(
|
|
3998
|
+
packageName,
|
|
3999
|
+
productId,
|
|
4000
|
+
purchaseOptionId,
|
|
4001
|
+
offerId,
|
|
4002
|
+
data,
|
|
4003
|
+
mask
|
|
4004
|
+
);
|
|
3943
4005
|
} catch (error) {
|
|
3944
4006
|
throw new GpcError(
|
|
3945
4007
|
`Failed to update offer "${offerId}" for product "${productId}": ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -3964,9 +4026,9 @@ async function diffOneTimeProduct(client, packageName, productId, localData) {
|
|
|
3964
4026
|
}
|
|
3965
4027
|
return diffs;
|
|
3966
4028
|
}
|
|
3967
|
-
async function deleteOneTimeOffer(client, packageName, productId, offerId) {
|
|
4029
|
+
async function deleteOneTimeOffer(client, packageName, productId, offerId, purchaseOptionId = "-") {
|
|
3968
4030
|
try {
|
|
3969
|
-
await client.oneTimeProducts.deleteOffer(packageName, productId, offerId);
|
|
4031
|
+
await client.oneTimeProducts.deleteOffer(packageName, productId, purchaseOptionId, offerId);
|
|
3970
4032
|
} catch (error) {
|
|
3971
4033
|
throw new GpcError(
|
|
3972
4034
|
`Failed to delete offer "${offerId}" for product "${productId}": ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -4197,7 +4259,13 @@ async function advanceTrain(apiClient, reportingClient, packageName) {
|
|
|
4197
4259
|
}
|
|
4198
4260
|
async function executeStage(apiClient, packageName, state, stageIndex) {
|
|
4199
4261
|
const stage = state.stages[stageIndex];
|
|
4200
|
-
if (!stage)
|
|
4262
|
+
if (!stage)
|
|
4263
|
+
throw new GpcError(
|
|
4264
|
+
`Stage ${stageIndex} not found`,
|
|
4265
|
+
"TRAIN_STAGE_NOT_FOUND",
|
|
4266
|
+
1,
|
|
4267
|
+
"Check your release train configuration."
|
|
4268
|
+
);
|
|
4201
4269
|
const rolloutFraction = stage.rollout / 100;
|
|
4202
4270
|
await updateRollout(apiClient, packageName, stage.track, "increase", rolloutFraction);
|
|
4203
4271
|
stage.executedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4650,7 +4718,9 @@ var RESOURCE_IDS = {
|
|
|
4650
4718
|
16842754: "icon",
|
|
4651
4719
|
16842755: "name",
|
|
4652
4720
|
16842767: "versionCode",
|
|
4721
|
+
16843291: "versionCode",
|
|
4653
4722
|
16842768: "versionName",
|
|
4723
|
+
16843292: "versionName",
|
|
4654
4724
|
16843276: "minSdkVersion",
|
|
4655
4725
|
16843376: "targetSdkVersion",
|
|
4656
4726
|
16842782: "debuggable",
|
|
@@ -4666,27 +4736,39 @@ var RESOURCE_IDS = {
|
|
|
4666
4736
|
function buildXmlSchema() {
|
|
4667
4737
|
const root = new protobuf.Root();
|
|
4668
4738
|
const ns = root.define("aapt.pb");
|
|
4669
|
-
const
|
|
4670
|
-
const
|
|
4739
|
+
const NullType = new protobuf.Type("NullType");
|
|
4740
|
+
const EmptyType = new protobuf.Type("EmptyType");
|
|
4671
4741
|
const Primitive = new protobuf.Type("Primitive").add(
|
|
4672
|
-
new protobuf.OneOf("oneofValue").add(new protobuf.Field("intDecimalValue", 6, "int32")).add(new protobuf.Field("intHexadecimalValue", 7, "uint32")).add(new protobuf.Field("booleanValue", 8, "bool")).add(new protobuf.Field("
|
|
4673
|
-
);
|
|
4674
|
-
const Reference = new protobuf.Type("Reference").add(new protobuf.Field("id", 1, "uint32")).add(new protobuf.Field("name", 2, "string"));
|
|
4675
|
-
const Item = new protobuf.Type("Item").add(
|
|
4676
|
-
new protobuf.OneOf("value").add(new protobuf.Field("ref", 1, "Reference")).add(new protobuf.Field("str", 2, "String")).add(new protobuf.Field("prim", 4, "Primitive"))
|
|
4742
|
+
new protobuf.OneOf("oneofValue").add(new protobuf.Field("nullValue", 1, "NullType")).add(new protobuf.Field("emptyValue", 2, "EmptyType")).add(new protobuf.Field("floatValue", 3, "float")).add(new protobuf.Field("intDecimalValue", 6, "int32")).add(new protobuf.Field("intHexadecimalValue", 7, "uint32")).add(new protobuf.Field("booleanValue", 8, "bool")).add(new protobuf.Field("colorArgb8Value", 9, "uint32")).add(new protobuf.Field("colorRgb8Value", 10, "uint32")).add(new protobuf.Field("colorArgb4Value", 11, "uint32")).add(new protobuf.Field("colorRgb4Value", 12, "uint32")).add(new protobuf.Field("dimensionValue", 13, "uint32")).add(new protobuf.Field("fractionValue", 14, "uint32"))
|
|
4677
4743
|
);
|
|
4744
|
+
const Reference = new protobuf.Type("Reference").add(new protobuf.Field("type", 1, "uint32")).add(new protobuf.Field("id", 2, "uint32")).add(new protobuf.Field("name", 3, "string")).add(new protobuf.Field("isPrivate", 4, "bool")).add(new protobuf.Field("isDynamic", 5, "Boolean")).add(new protobuf.Field("typeFlags", 6, "uint32")).add(new protobuf.Field("allowRaw", 7, "bool"));
|
|
4678
4745
|
const StringMsg = new protobuf.Type("String").add(new protobuf.Field("value", 1, "string"));
|
|
4679
|
-
const
|
|
4680
|
-
const
|
|
4746
|
+
const RawString = new protobuf.Type("RawString").add(new protobuf.Field("value", 1, "string"));
|
|
4747
|
+
const StyledString = new protobuf.Type("StyledString").add(new protobuf.Field("value", 1, "string")).add(new protobuf.Field("span", 2, "Span", "repeated"));
|
|
4748
|
+
const Span = new protobuf.Type("Span").add(new protobuf.Field("tag", 1, "string")).add(new protobuf.Field("firstChar", 2, "uint32")).add(new protobuf.Field("lastChar", 3, "uint32"));
|
|
4749
|
+
const FileReference = new protobuf.Type("FileReference").add(new protobuf.Field("path", 1, "string")).add(new protobuf.Field("type", 2, "uint32"));
|
|
4750
|
+
const Id = new protobuf.Type("Id");
|
|
4751
|
+
const BooleanMsg = new protobuf.Type("Boolean").add(new protobuf.Field("value", 1, "bool"));
|
|
4752
|
+
const Item = new protobuf.Type("Item").add(
|
|
4753
|
+
new protobuf.OneOf("value").add(new protobuf.Field("ref", 1, "Reference")).add(new protobuf.Field("str", 2, "String")).add(new protobuf.Field("rawStr", 3, "RawString")).add(new protobuf.Field("styledStr", 4, "StyledString")).add(new protobuf.Field("file", 5, "FileReference")).add(new protobuf.Field("id", 6, "Id")).add(new protobuf.Field("prim", 7, "Primitive"))
|
|
4754
|
+
).add(new protobuf.Field("flagStatus", 8, "uint32")).add(new protobuf.Field("flagNegated", 9, "bool")).add(new protobuf.Field("flagName", 10, "string"));
|
|
4755
|
+
const XmlAttribute = new protobuf.Type("XmlAttribute").add(new protobuf.Field("namespaceUri", 1, "string")).add(new protobuf.Field("name", 2, "string")).add(new protobuf.Field("value", 3, "string")).add(new protobuf.Field("resourceId", 5, "uint32")).add(new protobuf.Field("compiledItem", 6, "Item"));
|
|
4756
|
+
const XmlNamespace = new protobuf.Type("XmlNamespace").add(new protobuf.Field("prefix", 1, "string")).add(new protobuf.Field("uri", 2, "string"));
|
|
4681
4757
|
const XmlElement = new protobuf.Type("XmlElement").add(new protobuf.Field("namespaceDeclaration", 1, "XmlNamespace", "repeated")).add(new protobuf.Field("namespaceUri", 2, "string")).add(new protobuf.Field("name", 3, "string")).add(new protobuf.Field("attribute", 4, "XmlAttribute", "repeated")).add(new protobuf.Field("child", 5, "XmlNode", "repeated"));
|
|
4682
4758
|
const XmlNode = new protobuf.Type("XmlNode").add(
|
|
4683
4759
|
new protobuf.OneOf("node").add(new protobuf.Field("element", 1, "XmlElement")).add(new protobuf.Field("text", 2, "string"))
|
|
4684
|
-
)
|
|
4685
|
-
ns.add(
|
|
4686
|
-
ns.add(
|
|
4760
|
+
);
|
|
4761
|
+
ns.add(NullType);
|
|
4762
|
+
ns.add(EmptyType);
|
|
4687
4763
|
ns.add(Primitive);
|
|
4764
|
+
ns.add(BooleanMsg);
|
|
4688
4765
|
ns.add(Reference);
|
|
4689
4766
|
ns.add(StringMsg);
|
|
4767
|
+
ns.add(RawString);
|
|
4768
|
+
ns.add(StyledString);
|
|
4769
|
+
ns.add(Span);
|
|
4770
|
+
ns.add(FileReference);
|
|
4771
|
+
ns.add(Id);
|
|
4690
4772
|
ns.add(Item);
|
|
4691
4773
|
ns.add(XmlAttribute);
|
|
4692
4774
|
ns.add(XmlNamespace);
|
|
@@ -4708,29 +4790,26 @@ function decodeManifest(buf) {
|
|
|
4708
4790
|
}
|
|
4709
4791
|
return extractManifestData(decoded.element);
|
|
4710
4792
|
}
|
|
4711
|
-
function getAttrValue(attrs, resId) {
|
|
4712
|
-
const attr = attrs.find((a) => a.resourceId === resId);
|
|
4713
|
-
if (!attr) return void 0;
|
|
4714
|
-
const ci = attr.compiledItem;
|
|
4715
|
-
if (ci?.str?.value !== void 0) return ci.str.value;
|
|
4716
|
-
if (ci?.ref?.name !== void 0) return ci.ref.name;
|
|
4717
|
-
return attr.value || void 0;
|
|
4718
|
-
}
|
|
4719
4793
|
function getAttrByName(attrs, name) {
|
|
4720
4794
|
const attr = attrs.find((a) => a.name === name || RESOURCE_IDS[a.resourceId] === name);
|
|
4721
4795
|
if (!attr) return void 0;
|
|
4722
4796
|
const ci = attr.compiledItem;
|
|
4723
4797
|
if (ci?.str?.value !== void 0) return ci.str.value;
|
|
4724
4798
|
if (ci?.ref?.name !== void 0) return ci.ref.name;
|
|
4725
|
-
|
|
4799
|
+
const prim = ci?.prim;
|
|
4800
|
+
if (attr.value) return attr.value;
|
|
4801
|
+
if (prim?.booleanValue === true) return "true";
|
|
4802
|
+
if (prim?.intDecimalValue) return String(prim.intDecimalValue);
|
|
4803
|
+
if (prim?.intHexadecimalValue) return String(prim.intHexadecimalValue);
|
|
4804
|
+
return void 0;
|
|
4726
4805
|
}
|
|
4727
|
-
function
|
|
4728
|
-
const val =
|
|
4806
|
+
function getBoolByName(attrs, name, defaultVal) {
|
|
4807
|
+
const val = getAttrByName(attrs, name);
|
|
4729
4808
|
if (val === void 0) return defaultVal;
|
|
4730
4809
|
return val === "true" || val === "1";
|
|
4731
4810
|
}
|
|
4732
|
-
function
|
|
4733
|
-
const val =
|
|
4811
|
+
function getIntByName(attrs, name, defaultVal) {
|
|
4812
|
+
const val = getAttrByName(attrs, name);
|
|
4734
4813
|
if (val === void 0) return defaultVal;
|
|
4735
4814
|
const n = parseInt(val, 10);
|
|
4736
4815
|
return isNaN(n) ? defaultVal : n;
|
|
@@ -4741,23 +4820,25 @@ function getChildren(elem, tagName) {
|
|
|
4741
4820
|
function extractManifestData(manifest) {
|
|
4742
4821
|
const attrs = manifest.attribute || [];
|
|
4743
4822
|
const packageName = getAttrByName(attrs, "package") || "";
|
|
4744
|
-
const
|
|
4745
|
-
const
|
|
4823
|
+
const versionCodeStr = getAttrByName(attrs, "versionCode");
|
|
4824
|
+
const versionCode = versionCodeStr ? parseInt(versionCodeStr, 10) || 0 : 0;
|
|
4825
|
+
const versionName = getAttrByName(attrs, "versionName") || "";
|
|
4746
4826
|
const usesSdkElements = getChildren(manifest, "uses-sdk");
|
|
4747
4827
|
const usesSdk = usesSdkElements[0];
|
|
4748
|
-
const minSdk = usesSdk ?
|
|
4749
|
-
const targetSdk = usesSdk ?
|
|
4750
|
-
const permissions = getChildren(manifest, "uses-permission").map((el) =>
|
|
4828
|
+
const minSdk = usesSdk ? getIntByName(usesSdk.attribute || [], "minSdkVersion", 1) : 1;
|
|
4829
|
+
const targetSdk = usesSdk ? getIntByName(usesSdk.attribute || [], "targetSdkVersion", minSdk) : minSdk;
|
|
4830
|
+
const permissions = getChildren(manifest, "uses-permission").map((el) => getAttrByName(el.attribute || [], "name")).filter((p) => p !== void 0);
|
|
4751
4831
|
const features = getChildren(manifest, "uses-feature").map((el) => ({
|
|
4752
|
-
name:
|
|
4753
|
-
required:
|
|
4832
|
+
name: getAttrByName(el.attribute || [], "name") || "",
|
|
4833
|
+
required: getBoolByName(el.attribute || [], "required", true)
|
|
4754
4834
|
}));
|
|
4755
4835
|
const appElements = getChildren(manifest, "application");
|
|
4756
4836
|
const app = appElements[0];
|
|
4757
|
-
const debuggable = app ?
|
|
4758
|
-
const testOnly =
|
|
4759
|
-
const usesCleartextTraffic = app ?
|
|
4760
|
-
const extractNativeLibs = app ?
|
|
4837
|
+
const debuggable = app ? getBoolByName(app.attribute || [], "debuggable", false) : false;
|
|
4838
|
+
const testOnly = getBoolByName(attrs, "testOnly", false);
|
|
4839
|
+
const usesCleartextTraffic = app ? getBoolByName(app.attribute || [], "usesCleartextTraffic", true) : true;
|
|
4840
|
+
const extractNativeLibs = app ? getBoolByName(app.attribute || [], "extractNativeLibs", true) : true;
|
|
4841
|
+
const pageSizeCompat = app ? getBoolByName(app.attribute || [], "pageSizeCompat", false) : false;
|
|
4761
4842
|
const activities = app ? extractComponents(app, "activity") : [];
|
|
4762
4843
|
const services = app ? extractComponents(app, "service") : [];
|
|
4763
4844
|
const receivers = app ? extractComponents(app, "receiver") : [];
|
|
@@ -4772,6 +4853,7 @@ function extractManifestData(manifest) {
|
|
|
4772
4853
|
testOnly,
|
|
4773
4854
|
usesCleartextTraffic,
|
|
4774
4855
|
extractNativeLibs,
|
|
4856
|
+
pageSizeCompat: pageSizeCompat || void 0,
|
|
4775
4857
|
permissions,
|
|
4776
4858
|
features,
|
|
4777
4859
|
activities,
|
|
@@ -4783,13 +4865,29 @@ function extractManifestData(manifest) {
|
|
|
4783
4865
|
function extractComponents(appElement, tagName) {
|
|
4784
4866
|
return getChildren(appElement, tagName).map((el) => {
|
|
4785
4867
|
const compAttrs = el.attribute || [];
|
|
4786
|
-
const exportedVal =
|
|
4787
|
-
const
|
|
4868
|
+
const exportedVal = getAttrByName(compAttrs, "exported");
|
|
4869
|
+
const intentFilters = getChildren(el, "intent-filter");
|
|
4870
|
+
const hasIntentFilter = intentFilters.length > 0;
|
|
4871
|
+
const intentActions = [];
|
|
4872
|
+
const intentCategories = [];
|
|
4873
|
+
for (const filter of intentFilters) {
|
|
4874
|
+
for (const action of getChildren(filter, "action")) {
|
|
4875
|
+
const name = getAttrByName(action.attribute || [], "name");
|
|
4876
|
+
if (name) intentActions.push(name);
|
|
4877
|
+
}
|
|
4878
|
+
for (const cat of getChildren(filter, "category")) {
|
|
4879
|
+
const name = getAttrByName(cat.attribute || [], "name");
|
|
4880
|
+
if (name) intentCategories.push(name);
|
|
4881
|
+
}
|
|
4882
|
+
}
|
|
4788
4883
|
return {
|
|
4789
|
-
name:
|
|
4884
|
+
name: getAttrByName(compAttrs, "name") || "",
|
|
4790
4885
|
exported: exportedVal === void 0 ? void 0 : exportedVal === "true" || exportedVal === "1",
|
|
4791
|
-
|
|
4792
|
-
|
|
4886
|
+
permission: getAttrByName(compAttrs, "permission") || void 0,
|
|
4887
|
+
foregroundServiceType: tagName === "service" ? getAttrByName(compAttrs, "foregroundServiceType") : void 0,
|
|
4888
|
+
hasIntentFilter,
|
|
4889
|
+
intentActions: intentActions.length > 0 ? intentActions : void 0,
|
|
4890
|
+
intentCategories: intentCategories.length > 0 ? intentCategories : void 0
|
|
4793
4891
|
};
|
|
4794
4892
|
});
|
|
4795
4893
|
}
|
|
@@ -4797,12 +4895,14 @@ function extractComponents(appElement, tagName) {
|
|
|
4797
4895
|
// src/preflight/aab-reader.ts
|
|
4798
4896
|
var AAB_MANIFEST_PATH = "base/manifest/AndroidManifest.xml";
|
|
4799
4897
|
var APK_MANIFEST_PATH = "AndroidManifest.xml";
|
|
4898
|
+
var SO_HEADER_BYTES = 256;
|
|
4899
|
+
var SO_PATH_RE = /\/lib\/[^/]+\/[^/]+\.so$/;
|
|
4800
4900
|
function detectManifestPath(filePath) {
|
|
4801
4901
|
return filePath.toLowerCase().endsWith(".apk") ? APK_MANIFEST_PATH : AAB_MANIFEST_PATH;
|
|
4802
4902
|
}
|
|
4803
4903
|
async function readAab(aabPath) {
|
|
4804
4904
|
const manifestPath = detectManifestPath(aabPath);
|
|
4805
|
-
const { zipfile, entries, manifestBuf } = await openAndScan(aabPath, manifestPath);
|
|
4905
|
+
const { zipfile, entries, manifestBuf, soHeaders } = await openAndScan(aabPath, manifestPath);
|
|
4806
4906
|
zipfile.close();
|
|
4807
4907
|
if (!manifestBuf) {
|
|
4808
4908
|
const fileType = aabPath.toLowerCase().endsWith(".apk") ? "APK" : "AAB";
|
|
@@ -4818,7 +4918,7 @@ async function readAab(aabPath) {
|
|
|
4818
4918
|
manifest = createFallbackManifest();
|
|
4819
4919
|
manifest._parseError = `Manifest could not be fully parsed: ${errMsg}. Manifest-dependent checks will be skipped.`;
|
|
4820
4920
|
}
|
|
4821
|
-
return { manifest, entries };
|
|
4921
|
+
return { manifest, entries, nativeLibHeaders: soHeaders };
|
|
4822
4922
|
}
|
|
4823
4923
|
function createFallbackManifest() {
|
|
4824
4924
|
return {
|
|
@@ -4847,8 +4947,11 @@ function openAndScan(aabPath, manifestPath = AAB_MANIFEST_PATH) {
|
|
|
4847
4947
|
return;
|
|
4848
4948
|
}
|
|
4849
4949
|
const entries = [];
|
|
4950
|
+
const soHeaders = /* @__PURE__ */ new Map();
|
|
4850
4951
|
let manifestBuf = null;
|
|
4851
4952
|
let rejected = false;
|
|
4953
|
+
let pendingStreams = 0;
|
|
4954
|
+
let entrysDone = false;
|
|
4852
4955
|
function fail(error) {
|
|
4853
4956
|
if (!rejected) {
|
|
4854
4957
|
rejected = true;
|
|
@@ -4856,6 +4959,46 @@ function openAndScan(aabPath, manifestPath = AAB_MANIFEST_PATH) {
|
|
|
4856
4959
|
reject(error);
|
|
4857
4960
|
}
|
|
4858
4961
|
}
|
|
4962
|
+
function maybeResolve() {
|
|
4963
|
+
if (!rejected && entrysDone && pendingStreams === 0) {
|
|
4964
|
+
resolve2({ zipfile, entries, manifestBuf, soHeaders });
|
|
4965
|
+
}
|
|
4966
|
+
}
|
|
4967
|
+
function readEntryStream(entry, onData, maxBytes) {
|
|
4968
|
+
pendingStreams++;
|
|
4969
|
+
zipfile.openReadStream(entry, (streamErr, stream) => {
|
|
4970
|
+
if (streamErr || !stream) {
|
|
4971
|
+
fail(streamErr ?? new Error(`Failed to read ${entry.fileName}`));
|
|
4972
|
+
return;
|
|
4973
|
+
}
|
|
4974
|
+
const chunks = [];
|
|
4975
|
+
let totalBytes = 0;
|
|
4976
|
+
let done = false;
|
|
4977
|
+
stream.on("data", (chunk) => {
|
|
4978
|
+
chunks.push(chunk);
|
|
4979
|
+
totalBytes += chunk.length;
|
|
4980
|
+
if (maxBytes && totalBytes >= maxBytes) {
|
|
4981
|
+
stream.destroy();
|
|
4982
|
+
}
|
|
4983
|
+
});
|
|
4984
|
+
stream.on("error", (e) => {
|
|
4985
|
+
if (maxBytes && totalBytes >= maxBytes) return;
|
|
4986
|
+
fail(e);
|
|
4987
|
+
});
|
|
4988
|
+
const finish = () => {
|
|
4989
|
+
if (done) return;
|
|
4990
|
+
done = true;
|
|
4991
|
+
const buf = Buffer.concat(chunks);
|
|
4992
|
+
onData(maxBytes ? buf.subarray(0, maxBytes) : buf);
|
|
4993
|
+
pendingStreams--;
|
|
4994
|
+
maybeResolve();
|
|
4995
|
+
};
|
|
4996
|
+
stream.on("end", finish);
|
|
4997
|
+
stream.on("close", () => {
|
|
4998
|
+
if (totalBytes > 0 && maxBytes) finish();
|
|
4999
|
+
});
|
|
5000
|
+
});
|
|
5001
|
+
}
|
|
4859
5002
|
zipfile.on("error", fail);
|
|
4860
5003
|
zipfile.on("entry", (entry) => {
|
|
4861
5004
|
if (rejected) return;
|
|
@@ -4868,27 +5011,23 @@ function openAndScan(aabPath, manifestPath = AAB_MANIFEST_PATH) {
|
|
|
4868
5011
|
});
|
|
4869
5012
|
}
|
|
4870
5013
|
if (path === manifestPath) {
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
fail(streamErr ?? new Error("Failed to read manifest entry"));
|
|
4874
|
-
return;
|
|
4875
|
-
}
|
|
4876
|
-
const chunks = [];
|
|
4877
|
-
stream.on("data", (chunk) => chunks.push(chunk));
|
|
4878
|
-
stream.on("error", (e) => fail(e));
|
|
4879
|
-
stream.on("end", () => {
|
|
4880
|
-
manifestBuf = Buffer.concat(chunks);
|
|
4881
|
-
zipfile.readEntry();
|
|
4882
|
-
});
|
|
5014
|
+
readEntryStream(entry, (buf) => {
|
|
5015
|
+
manifestBuf = buf;
|
|
4883
5016
|
});
|
|
4884
|
-
} else {
|
|
4885
|
-
|
|
5017
|
+
} else if (SO_PATH_RE.test(path)) {
|
|
5018
|
+
readEntryStream(
|
|
5019
|
+
entry,
|
|
5020
|
+
(buf) => {
|
|
5021
|
+
soHeaders.set(path, buf);
|
|
5022
|
+
},
|
|
5023
|
+
SO_HEADER_BYTES
|
|
5024
|
+
);
|
|
4886
5025
|
}
|
|
5026
|
+
zipfile.readEntry();
|
|
4887
5027
|
});
|
|
4888
5028
|
zipfile.on("end", () => {
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
}
|
|
5029
|
+
entrysDone = true;
|
|
5030
|
+
maybeResolve();
|
|
4892
5031
|
});
|
|
4893
5032
|
zipfile.readEntry();
|
|
4894
5033
|
});
|
|
@@ -4957,13 +5096,13 @@ var manifestScanner = {
|
|
|
4957
5096
|
});
|
|
4958
5097
|
}
|
|
4959
5098
|
if (manifest.targetSdk >= 31) {
|
|
4960
|
-
const
|
|
5099
|
+
const allComponents2 = [
|
|
4961
5100
|
...manifest.activities,
|
|
4962
5101
|
...manifest.services,
|
|
4963
5102
|
...manifest.receivers,
|
|
4964
5103
|
...manifest.providers
|
|
4965
5104
|
];
|
|
4966
|
-
for (const comp of
|
|
5105
|
+
for (const comp of allComponents2) {
|
|
4967
5106
|
if (comp.hasIntentFilter && comp.exported === void 0) {
|
|
4968
5107
|
findings.push({
|
|
4969
5108
|
scanner: "manifest",
|
|
@@ -4995,6 +5134,27 @@ var manifestScanner = {
|
|
|
4995
5134
|
}
|
|
4996
5135
|
}
|
|
4997
5136
|
}
|
|
5137
|
+
const allComponents = [
|
|
5138
|
+
...manifest.activities,
|
|
5139
|
+
...manifest.services,
|
|
5140
|
+
...manifest.receivers,
|
|
5141
|
+
...manifest.providers
|
|
5142
|
+
];
|
|
5143
|
+
for (const comp of allComponents) {
|
|
5144
|
+
if (comp.exported !== true) continue;
|
|
5145
|
+
if (comp.permission) continue;
|
|
5146
|
+
const isLauncher = comp.intentActions?.includes("android.intent.action.MAIN") && comp.intentCategories?.includes("android.intent.category.LAUNCHER");
|
|
5147
|
+
if (isLauncher) continue;
|
|
5148
|
+
findings.push({
|
|
5149
|
+
scanner: "manifest",
|
|
5150
|
+
ruleId: "exported-no-permission",
|
|
5151
|
+
severity: "warning",
|
|
5152
|
+
title: `Exported component "${comp.name}" has no permission`,
|
|
5153
|
+
message: `"${comp.name}" is exported without an android:permission attribute. Any app on the device can access this component.`,
|
|
5154
|
+
suggestion: `Add android:permission to restrict access, or set android:exported="false" if external access is not needed.`,
|
|
5155
|
+
policyUrl: "https://developer.android.com/privacy-and-security/security-tips#components"
|
|
5156
|
+
});
|
|
5157
|
+
}
|
|
4998
5158
|
if (manifest.minSdk < 21) {
|
|
4999
5159
|
findings.push({
|
|
5000
5160
|
scanner: "manifest",
|
|
@@ -5225,9 +5385,53 @@ var permissionsScanner = {
|
|
|
5225
5385
|
// src/preflight/scanners/native-libs-scanner.ts
|
|
5226
5386
|
var KNOWN_ABIS = ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"];
|
|
5227
5387
|
var LIB_PATH_RE = /^(?:[^/]+\/)?lib\/([^/]+)\/[^/]+\.so$/;
|
|
5388
|
+
var ELF_MAGIC = 2135247942;
|
|
5389
|
+
var PT_LOAD = 1;
|
|
5390
|
+
var PAGE_16KB = 16384;
|
|
5391
|
+
function checkElfAlignment(buf) {
|
|
5392
|
+
if (buf.length < 52) return null;
|
|
5393
|
+
if (buf.readUInt32BE(0) !== ELF_MAGIC) return null;
|
|
5394
|
+
const is64 = buf[4] === 2;
|
|
5395
|
+
const isLE = buf[5] === 1;
|
|
5396
|
+
const read16 = isLE ? (o) => buf.readUInt16LE(o) : (o) => buf.readUInt16BE(o);
|
|
5397
|
+
const read32 = isLE ? (o) => buf.readUInt32LE(o) : (o) => buf.readUInt32BE(o);
|
|
5398
|
+
const read64 = isLE ? (o) => Number(buf.readBigUInt64LE(o)) : (o) => Number(buf.readBigUInt64BE(o));
|
|
5399
|
+
let phOff, phSize, phNum;
|
|
5400
|
+
if (is64) {
|
|
5401
|
+
if (buf.length < 64) return null;
|
|
5402
|
+
phOff = read64(32);
|
|
5403
|
+
phSize = read16(54);
|
|
5404
|
+
phNum = read16(56);
|
|
5405
|
+
} else {
|
|
5406
|
+
phOff = read32(28);
|
|
5407
|
+
phSize = read16(42);
|
|
5408
|
+
phNum = read16(44);
|
|
5409
|
+
}
|
|
5410
|
+
if (phNum === 0 || phSize === 0) return null;
|
|
5411
|
+
let minAlign = Infinity;
|
|
5412
|
+
let foundLoad = false;
|
|
5413
|
+
for (let i = 0; i < phNum; i++) {
|
|
5414
|
+
const off = phOff + i * phSize;
|
|
5415
|
+
if (off + phSize > buf.length) break;
|
|
5416
|
+
const pType = read32(off);
|
|
5417
|
+
if (pType !== PT_LOAD) continue;
|
|
5418
|
+
foundLoad = true;
|
|
5419
|
+
const pAlign = is64 ? read64(off + 48) : read32(off + 28);
|
|
5420
|
+
if (pAlign > 0 && pAlign < minAlign) {
|
|
5421
|
+
minAlign = pAlign;
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5424
|
+
if (!foundLoad) return null;
|
|
5425
|
+
return { minAlign: minAlign === Infinity ? 0 : minAlign, is64 };
|
|
5426
|
+
}
|
|
5427
|
+
function alignmentLabel(align) {
|
|
5428
|
+
if (align <= 0) return "unknown";
|
|
5429
|
+
const log2 = Math.log2(align);
|
|
5430
|
+
return Number.isInteger(log2) ? `${align / 1024}KB (2^${log2})` : `${align} bytes`;
|
|
5431
|
+
}
|
|
5228
5432
|
var nativeLibsScanner = {
|
|
5229
5433
|
name: "native-libs",
|
|
5230
|
-
description: "Checks native library architectures
|
|
5434
|
+
description: "Checks native library architectures, 64-bit compliance, and 16KB alignment",
|
|
5231
5435
|
requires: ["zipEntries"],
|
|
5232
5436
|
async scan(ctx) {
|
|
5233
5437
|
const entries = ctx.zipEntries;
|
|
@@ -5270,6 +5474,7 @@ var nativeLibsScanner = {
|
|
|
5270
5474
|
policyUrl: "https://developer.android.com/google/play/requirements/64-bit"
|
|
5271
5475
|
});
|
|
5272
5476
|
}
|
|
5477
|
+
check16KBAlignment(ctx.nativeLibHeaders, ctx.manifest, findings);
|
|
5273
5478
|
const detectedAbis = KNOWN_ABIS.filter((abi) => abisFound.has(abi));
|
|
5274
5479
|
const unknownAbis = [...abisFound].filter(
|
|
5275
5480
|
(abi) => !KNOWN_ABIS.includes(abi)
|
|
@@ -5296,6 +5501,33 @@ var nativeLibsScanner = {
|
|
|
5296
5501
|
return findings;
|
|
5297
5502
|
}
|
|
5298
5503
|
};
|
|
5504
|
+
function check16KBAlignment(headers, manifest, findings) {
|
|
5505
|
+
if (!headers || headers.size === 0) return;
|
|
5506
|
+
const nonCompliant = [];
|
|
5507
|
+
for (const [path, buf] of headers) {
|
|
5508
|
+
const result = checkElfAlignment(buf);
|
|
5509
|
+
if (!result) continue;
|
|
5510
|
+
if (result.minAlign < PAGE_16KB) {
|
|
5511
|
+
nonCompliant.push({ path, align: result.minAlign });
|
|
5512
|
+
}
|
|
5513
|
+
}
|
|
5514
|
+
if (nonCompliant.length === 0) return;
|
|
5515
|
+
const hasPageSizeCompat = manifest?.pageSizeCompat === true;
|
|
5516
|
+
const fileList = nonCompliant.map((f) => {
|
|
5517
|
+
const name = f.path.split("/").pop() ?? f.path;
|
|
5518
|
+
return ` ${name}: aligned to ${alignmentLabel(f.align)}, requires 16KB (2^14)`;
|
|
5519
|
+
}).join("\n");
|
|
5520
|
+
findings.push({
|
|
5521
|
+
scanner: "native-libs",
|
|
5522
|
+
ruleId: "native-libs-16kb-alignment",
|
|
5523
|
+
severity: hasPageSizeCompat ? "warning" : "critical",
|
|
5524
|
+
title: `${nonCompliant.length} native ${nonCompliant.length === 1 ? "library" : "libraries"} not 16KB aligned`,
|
|
5525
|
+
message: `Google Play requires 16KB page size alignment for all native libraries (enforced since Nov 2025).
|
|
5526
|
+
${fileList}` + (hasPageSizeCompat ? "\nandroid:pageSizeCompat is set, so the app will work via compatibility mode, but users on 16KB devices will see a compatibility dialog." : ""),
|
|
5527
|
+
suggestion: "Recompile with: -Wl,-z,max-page-size=16384\nOr upgrade to NDK r28+ (16KB aligned by default).\nOr set cmake flag: -DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON (NDK r27).",
|
|
5528
|
+
policyUrl: "https://developer.android.com/guide/practices/page-sizes"
|
|
5529
|
+
});
|
|
5530
|
+
}
|
|
5299
5531
|
|
|
5300
5532
|
// src/preflight/scanners/metadata-scanner.ts
|
|
5301
5533
|
import { readdir as readdir5, stat as stat8, readFile as readFile10 } from "fs/promises";
|
|
@@ -5993,6 +6225,7 @@ async function runPreflight(options) {
|
|
|
5993
6225
|
const aab = await readAab(options.aabPath);
|
|
5994
6226
|
ctx.manifest = aab.manifest;
|
|
5995
6227
|
ctx.zipEntries = aab.entries;
|
|
6228
|
+
ctx.nativeLibHeaders = aab.nativeLibHeaders;
|
|
5996
6229
|
if (aab.manifest._parseError) {
|
|
5997
6230
|
earlyFindings.push({
|
|
5998
6231
|
scanner: "manifest-parser",
|
|
@@ -6403,68 +6636,6 @@ async function downloadGeneratedApk(client, packageName, versionCode, apkId, out
|
|
|
6403
6636
|
return { path: outputPath, sizeBytes: bytes.byteLength };
|
|
6404
6637
|
}
|
|
6405
6638
|
|
|
6406
|
-
// src/commands/purchase-options.ts
|
|
6407
|
-
async function listPurchaseOptions(client, packageName) {
|
|
6408
|
-
try {
|
|
6409
|
-
return await client.purchaseOptions.list(packageName);
|
|
6410
|
-
} catch (error) {
|
|
6411
|
-
throw new GpcError(
|
|
6412
|
-
`Failed to list purchase options: ${error instanceof Error ? error.message : String(error)}`,
|
|
6413
|
-
"PURCHASE_OPTIONS_LIST_FAILED",
|
|
6414
|
-
4,
|
|
6415
|
-
"Check your package name and API permissions."
|
|
6416
|
-
);
|
|
6417
|
-
}
|
|
6418
|
-
}
|
|
6419
|
-
async function getPurchaseOption(client, packageName, purchaseOptionId) {
|
|
6420
|
-
try {
|
|
6421
|
-
return await client.purchaseOptions.get(packageName, purchaseOptionId);
|
|
6422
|
-
} catch (error) {
|
|
6423
|
-
throw new GpcError(
|
|
6424
|
-
`Failed to get purchase option "${purchaseOptionId}": ${error instanceof Error ? error.message : String(error)}`,
|
|
6425
|
-
"PURCHASE_OPTION_GET_FAILED",
|
|
6426
|
-
4,
|
|
6427
|
-
"Check that the purchase option ID exists."
|
|
6428
|
-
);
|
|
6429
|
-
}
|
|
6430
|
-
}
|
|
6431
|
-
async function createPurchaseOption(client, packageName, data) {
|
|
6432
|
-
try {
|
|
6433
|
-
return await client.purchaseOptions.create(packageName, data);
|
|
6434
|
-
} catch (error) {
|
|
6435
|
-
throw new GpcError(
|
|
6436
|
-
`Failed to create purchase option: ${error instanceof Error ? error.message : String(error)}`,
|
|
6437
|
-
"PURCHASE_OPTION_CREATE_FAILED",
|
|
6438
|
-
4,
|
|
6439
|
-
"Check your purchase option data and API permissions."
|
|
6440
|
-
);
|
|
6441
|
-
}
|
|
6442
|
-
}
|
|
6443
|
-
async function activatePurchaseOption(client, packageName, purchaseOptionId) {
|
|
6444
|
-
try {
|
|
6445
|
-
return await client.purchaseOptions.activate(packageName, purchaseOptionId);
|
|
6446
|
-
} catch (error) {
|
|
6447
|
-
throw new GpcError(
|
|
6448
|
-
`Failed to activate purchase option "${purchaseOptionId}": ${error instanceof Error ? error.message : String(error)}`,
|
|
6449
|
-
"PURCHASE_OPTION_ACTIVATE_FAILED",
|
|
6450
|
-
4,
|
|
6451
|
-
"Check that the purchase option exists and is in a valid state for activation."
|
|
6452
|
-
);
|
|
6453
|
-
}
|
|
6454
|
-
}
|
|
6455
|
-
async function deactivatePurchaseOption(client, packageName, purchaseOptionId) {
|
|
6456
|
-
try {
|
|
6457
|
-
return await client.purchaseOptions.deactivate(packageName, purchaseOptionId);
|
|
6458
|
-
} catch (error) {
|
|
6459
|
-
throw new GpcError(
|
|
6460
|
-
`Failed to deactivate purchase option "${purchaseOptionId}": ${error instanceof Error ? error.message : String(error)}`,
|
|
6461
|
-
"PURCHASE_OPTION_DEACTIVATE_FAILED",
|
|
6462
|
-
4,
|
|
6463
|
-
"Check that the purchase option exists and is in a valid state for deactivation."
|
|
6464
|
-
);
|
|
6465
|
-
}
|
|
6466
|
-
}
|
|
6467
|
-
|
|
6468
6639
|
// src/commands/bundle-analysis.ts
|
|
6469
6640
|
import { readFile as readFile14, stat as stat10 } from "fs/promises";
|
|
6470
6641
|
var EOCD_SIGNATURE = 101010256;
|
|
@@ -7149,21 +7320,15 @@ async function fetchChangelog(options) {
|
|
|
7149
7320
|
signal: controller.signal
|
|
7150
7321
|
});
|
|
7151
7322
|
} catch {
|
|
7152
|
-
throw new Error(
|
|
7153
|
-
`Could not fetch changelog. View online: ${DOCS_CHANGELOG_URL}`
|
|
7154
|
-
);
|
|
7323
|
+
throw new Error(`Could not fetch changelog. View online: ${DOCS_CHANGELOG_URL}`);
|
|
7155
7324
|
} finally {
|
|
7156
7325
|
clearTimeout(timer);
|
|
7157
7326
|
}
|
|
7158
7327
|
if (!response.ok) {
|
|
7159
7328
|
if (response.status === 404 && options?.version) {
|
|
7160
|
-
throw new Error(
|
|
7161
|
-
`Version ${options.version} not found. Run: gpc changelog --limit 10`
|
|
7162
|
-
);
|
|
7329
|
+
throw new Error(`Version ${options.version} not found. Run: gpc changelog --limit 10`);
|
|
7163
7330
|
}
|
|
7164
|
-
throw new Error(
|
|
7165
|
-
`GitHub API returned ${response.status}. View online: ${DOCS_CHANGELOG_URL}`
|
|
7166
|
-
);
|
|
7331
|
+
throw new Error(`GitHub API returned ${response.status}. View online: ${DOCS_CHANGELOG_URL}`);
|
|
7167
7332
|
}
|
|
7168
7333
|
const data = await response.json();
|
|
7169
7334
|
const releases = Array.isArray(data) ? data : [data];
|
|
@@ -7212,7 +7377,7 @@ async function getRtdnStatus(client, packageName) {
|
|
|
7212
7377
|
try {
|
|
7213
7378
|
const edit = await client.edits.insert(packageName);
|
|
7214
7379
|
try {
|
|
7215
|
-
|
|
7380
|
+
await client.details.get(packageName, edit.id);
|
|
7216
7381
|
return {
|
|
7217
7382
|
topicName: null,
|
|
7218
7383
|
enabled: false
|
|
@@ -7296,7 +7461,6 @@ export {
|
|
|
7296
7461
|
acknowledgeProductPurchase,
|
|
7297
7462
|
activateBasePlan,
|
|
7298
7463
|
activateOffer,
|
|
7299
|
-
activatePurchaseOption,
|
|
7300
7464
|
addRecoveryTargeting,
|
|
7301
7465
|
addTesters,
|
|
7302
7466
|
advanceTrain,
|
|
@@ -7326,14 +7490,12 @@ export {
|
|
|
7326
7490
|
createOffer,
|
|
7327
7491
|
createOneTimeOffer,
|
|
7328
7492
|
createOneTimeProduct,
|
|
7329
|
-
createPurchaseOption,
|
|
7330
7493
|
createRecoveryAction,
|
|
7331
7494
|
createSpinner,
|
|
7332
7495
|
createSubscription,
|
|
7333
7496
|
createTrack,
|
|
7334
7497
|
deactivateBasePlan,
|
|
7335
7498
|
deactivateOffer,
|
|
7336
|
-
deactivatePurchaseOption,
|
|
7337
7499
|
decodeNotification,
|
|
7338
7500
|
deferSubscriptionPurchase,
|
|
7339
7501
|
deferSubscriptionV2,
|
|
@@ -7391,7 +7553,6 @@ export {
|
|
|
7391
7553
|
getOrderDetails,
|
|
7392
7554
|
getProductPurchase,
|
|
7393
7555
|
getProductPurchaseV2,
|
|
7394
|
-
getPurchaseOption,
|
|
7395
7556
|
getQuotaUsage,
|
|
7396
7557
|
getReleasesStatus,
|
|
7397
7558
|
getReview,
|
|
@@ -7436,7 +7597,6 @@ export {
|
|
|
7436
7597
|
listOffers,
|
|
7437
7598
|
listOneTimeOffers,
|
|
7438
7599
|
listOneTimeProducts,
|
|
7439
|
-
listPurchaseOptions,
|
|
7440
7600
|
listRecoveryActions,
|
|
7441
7601
|
listReports,
|
|
7442
7602
|
listReviews,
|
|
@@ -7464,7 +7624,6 @@ export {
|
|
|
7464
7624
|
redactSensitive,
|
|
7465
7625
|
refundExternalTransaction,
|
|
7466
7626
|
refundOrder,
|
|
7467
|
-
refundSubscriptionV2,
|
|
7468
7627
|
relativeTime,
|
|
7469
7628
|
removeTesters,
|
|
7470
7629
|
removeUser,
|