@gpc-cli/core 0.9.43 → 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.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(packageName, edit.id, filePath, uploadOpts, options.deviceTierConfigId);
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,
@@ -2452,16 +2455,20 @@ function analyzeReviews(reviews) {
2452
2455
  async function listReviews(client, packageName, options) {
2453
2456
  let reviews;
2454
2457
  if (options?.all) {
2455
- const { items } = await paginateAll(async (pageToken) => {
2456
- const apiOptions = { token: pageToken };
2457
- if (options?.translationLanguage) apiOptions.translationLanguage = options.translationLanguage;
2458
- if (options?.maxResults) apiOptions.maxResults = options.maxResults;
2459
- const response = await client.reviews.list(packageName, apiOptions);
2460
- return {
2461
- items: response.reviews || [],
2462
- nextPageToken: response.tokenPagination?.nextPageToken
2463
- };
2464
- }, { limit: options?.limit });
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
+ );
2465
2472
  reviews = items;
2466
2473
  } else {
2467
2474
  const apiOptions = {};
@@ -3336,10 +3343,6 @@ async function revokeSubscriptionPurchase(client, packageName, token) {
3336
3343
  validatePackageName(packageName);
3337
3344
  return client.purchases.revokeSubscriptionV2(packageName, token);
3338
3345
  }
3339
- async function refundSubscriptionV2(client, packageName, token) {
3340
- validatePackageName(packageName);
3341
- return client.purchases.refundSubscriptionV2(packageName, token);
3342
- }
3343
3346
  async function listVoidedPurchases(client, packageName, options) {
3344
3347
  validatePackageName(packageName);
3345
3348
  if (options?.limit || options?.nextPage) {
@@ -3375,10 +3378,20 @@ async function getOrderDetails(client, packageName, orderId) {
3375
3378
  async function batchGetOrders(client, packageName, orderIds) {
3376
3379
  validatePackageName(packageName);
3377
3380
  if (orderIds.length === 0) {
3378
- throw new GpcError("No order IDs provided", "ORDERS_BATCH_EMPTY", 2, "Pass at least one order ID with --ids");
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
+ );
3379
3387
  }
3380
3388
  if (orderIds.length > 1e3) {
3381
- throw new GpcError(`Too many order IDs (${orderIds.length}). Maximum is 1000.`, "ORDERS_BATCH_LIMIT", 2, "Split into multiple requests of 1000 or fewer");
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
+ );
3382
3395
  }
3383
3396
  return client.orders.batchGet(packageName, orderIds);
3384
3397
  }
@@ -3504,8 +3517,22 @@ async function listUsers(client, developerId, options) {
3504
3517
  const response = await client.list(developerId, options);
3505
3518
  return { users: response.users || [], nextPageToken: response.nextPageToken };
3506
3519
  }
3507
- async function getUser(client, developerId, userId) {
3508
- return client.get(developerId, userId);
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
+ );
3509
3536
  }
3510
3537
  async function inviteUser(client, developerId, email, permissions, grants) {
3511
3538
  const user = { email };
@@ -3899,7 +3926,7 @@ var OTP_ID_FIELDS = /* @__PURE__ */ new Set(["productId", "packageName"]);
3899
3926
  function deriveOtpUpdateMask(data) {
3900
3927
  return Object.keys(data).filter((k) => !OTP_ID_FIELDS.has(k)).join(",");
3901
3928
  }
3902
- var OTP_OFFER_ID_FIELDS = /* @__PURE__ */ new Set(["productId", "offerId"]);
3929
+ var OTP_OFFER_ID_FIELDS = /* @__PURE__ */ new Set(["packageName", "productId", "purchaseOptionId", "offerId"]);
3903
3930
  function deriveOtpOfferUpdateMask(data) {
3904
3931
  return Object.keys(data).filter((k) => !OTP_OFFER_ID_FIELDS.has(k)).join(",");
3905
3932
  }
@@ -3928,9 +3955,9 @@ async function deleteOneTimeProduct(client, packageName, productId) {
3928
3955
  );
3929
3956
  }
3930
3957
  }
3931
- async function listOneTimeOffers(client, packageName, productId) {
3958
+ async function listOneTimeOffers(client, packageName, productId, purchaseOptionId = "-") {
3932
3959
  try {
3933
- return await client.oneTimeProducts.listOffers(packageName, productId);
3960
+ return await client.oneTimeProducts.listOffers(packageName, productId, purchaseOptionId);
3934
3961
  } catch (error) {
3935
3962
  throw new GpcError(
3936
3963
  `Failed to list offers for product "${productId}": ${error instanceof Error ? error.message : String(error)}`,
@@ -3940,9 +3967,9 @@ async function listOneTimeOffers(client, packageName, productId) {
3940
3967
  );
3941
3968
  }
3942
3969
  }
3943
- async function getOneTimeOffer(client, packageName, productId, offerId) {
3970
+ async function getOneTimeOffer(client, packageName, productId, offerId, purchaseOptionId = "-") {
3944
3971
  try {
3945
- return await client.oneTimeProducts.getOffer(packageName, productId, offerId);
3972
+ return await client.oneTimeProducts.getOffer(packageName, productId, purchaseOptionId, offerId);
3946
3973
  } catch (error) {
3947
3974
  throw new GpcError(
3948
3975
  `Failed to get offer "${offerId}" for product "${productId}": ${error instanceof Error ? error.message : String(error)}`,
@@ -3952,9 +3979,9 @@ async function getOneTimeOffer(client, packageName, productId, offerId) {
3952
3979
  );
3953
3980
  }
3954
3981
  }
3955
- async function createOneTimeOffer(client, packageName, productId, data) {
3982
+ async function createOneTimeOffer(client, packageName, productId, data, purchaseOptionId = "-") {
3956
3983
  try {
3957
- return await client.oneTimeProducts.createOffer(packageName, productId, data);
3984
+ return await client.oneTimeProducts.createOffer(packageName, productId, purchaseOptionId, data);
3958
3985
  } catch (error) {
3959
3986
  throw new GpcError(
3960
3987
  `Failed to create offer for product "${productId}": ${error instanceof Error ? error.message : String(error)}`,
@@ -3964,10 +3991,17 @@ async function createOneTimeOffer(client, packageName, productId, data) {
3964
3991
  );
3965
3992
  }
3966
3993
  }
3967
- async function updateOneTimeOffer(client, packageName, productId, offerId, data, updateMask) {
3994
+ async function updateOneTimeOffer(client, packageName, productId, offerId, data, updateMask, purchaseOptionId = "-") {
3968
3995
  try {
3969
3996
  const mask = updateMask || deriveOtpOfferUpdateMask(data);
3970
- return await client.oneTimeProducts.updateOffer(packageName, productId, offerId, data, mask);
3997
+ return await client.oneTimeProducts.updateOffer(
3998
+ packageName,
3999
+ productId,
4000
+ purchaseOptionId,
4001
+ offerId,
4002
+ data,
4003
+ mask
4004
+ );
3971
4005
  } catch (error) {
3972
4006
  throw new GpcError(
3973
4007
  `Failed to update offer "${offerId}" for product "${productId}": ${error instanceof Error ? error.message : String(error)}`,
@@ -3992,9 +4026,9 @@ async function diffOneTimeProduct(client, packageName, productId, localData) {
3992
4026
  }
3993
4027
  return diffs;
3994
4028
  }
3995
- async function deleteOneTimeOffer(client, packageName, productId, offerId) {
4029
+ async function deleteOneTimeOffer(client, packageName, productId, offerId, purchaseOptionId = "-") {
3996
4030
  try {
3997
- await client.oneTimeProducts.deleteOffer(packageName, productId, offerId);
4031
+ await client.oneTimeProducts.deleteOffer(packageName, productId, purchaseOptionId, offerId);
3998
4032
  } catch (error) {
3999
4033
  throw new GpcError(
4000
4034
  `Failed to delete offer "${offerId}" for product "${productId}": ${error instanceof Error ? error.message : String(error)}`,
@@ -4225,7 +4259,13 @@ async function advanceTrain(apiClient, reportingClient, packageName) {
4225
4259
  }
4226
4260
  async function executeStage(apiClient, packageName, state, stageIndex) {
4227
4261
  const stage = state.stages[stageIndex];
4228
- if (!stage) throw new GpcError(`Stage ${stageIndex} not found`, "TRAIN_STAGE_NOT_FOUND", 1, "Check your release train configuration.");
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
+ );
4229
4269
  const rolloutFraction = stage.rollout / 100;
4230
4270
  await updateRollout(apiClient, packageName, stage.track, "increase", rolloutFraction);
4231
4271
  stage.executedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -4678,7 +4718,9 @@ var RESOURCE_IDS = {
4678
4718
  16842754: "icon",
4679
4719
  16842755: "name",
4680
4720
  16842767: "versionCode",
4721
+ 16843291: "versionCode",
4681
4722
  16842768: "versionName",
4723
+ 16843292: "versionName",
4682
4724
  16843276: "minSdkVersion",
4683
4725
  16843376: "targetSdkVersion",
4684
4726
  16842782: "debuggable",
@@ -4694,27 +4736,39 @@ var RESOURCE_IDS = {
4694
4736
  function buildXmlSchema() {
4695
4737
  const root = new protobuf.Root();
4696
4738
  const ns = root.define("aapt.pb");
4697
- const Source = new protobuf.Type("Source").add(new protobuf.Field("pathIdx", 1, "uint32")).add(new protobuf.Field("position", 2, "Position"));
4698
- const Position = new protobuf.Type("Position").add(new protobuf.Field("lineNumber", 1, "uint32")).add(new protobuf.Field("columnNumber", 2, "uint32"));
4739
+ const NullType = new protobuf.Type("NullType");
4740
+ const EmptyType = new protobuf.Type("EmptyType");
4699
4741
  const Primitive = new protobuf.Type("Primitive").add(
4700
- 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("floatValue", 11, "float"))
4701
- );
4702
- const Reference = new protobuf.Type("Reference").add(new protobuf.Field("id", 1, "uint32")).add(new protobuf.Field("name", 2, "string"));
4703
- const Item = new protobuf.Type("Item").add(
4704
- 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"))
4705
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"));
4706
4745
  const StringMsg = new protobuf.Type("String").add(new protobuf.Field("value", 1, "string"));
4707
- 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("source", 4, "Source")).add(new protobuf.Field("resourceId", 5, "uint32")).add(new protobuf.Field("compiledItem", 6, "Item"));
4708
- const XmlNamespace = new protobuf.Type("XmlNamespace").add(new protobuf.Field("prefix", 1, "string")).add(new protobuf.Field("uri", 2, "string")).add(new protobuf.Field("source", 3, "Source"));
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"));
4709
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"));
4710
4758
  const XmlNode = new protobuf.Type("XmlNode").add(
4711
4759
  new protobuf.OneOf("node").add(new protobuf.Field("element", 1, "XmlElement")).add(new protobuf.Field("text", 2, "string"))
4712
- ).add(new protobuf.Field("source", 3, "Source"));
4713
- ns.add(Position);
4714
- ns.add(Source);
4760
+ );
4761
+ ns.add(NullType);
4762
+ ns.add(EmptyType);
4715
4763
  ns.add(Primitive);
4764
+ ns.add(BooleanMsg);
4716
4765
  ns.add(Reference);
4717
4766
  ns.add(StringMsg);
4767
+ ns.add(RawString);
4768
+ ns.add(StyledString);
4769
+ ns.add(Span);
4770
+ ns.add(FileReference);
4771
+ ns.add(Id);
4718
4772
  ns.add(Item);
4719
4773
  ns.add(XmlAttribute);
4720
4774
  ns.add(XmlNamespace);
@@ -4736,29 +4790,26 @@ function decodeManifest(buf) {
4736
4790
  }
4737
4791
  return extractManifestData(decoded.element);
4738
4792
  }
4739
- function getAttrValue(attrs, resId) {
4740
- const attr = attrs.find((a) => a.resourceId === resId);
4741
- if (!attr) return void 0;
4742
- const ci = attr.compiledItem;
4743
- if (ci?.str?.value !== void 0) return ci.str.value;
4744
- if (ci?.ref?.name !== void 0) return ci.ref.name;
4745
- return attr.value || void 0;
4746
- }
4747
4793
  function getAttrByName(attrs, name) {
4748
4794
  const attr = attrs.find((a) => a.name === name || RESOURCE_IDS[a.resourceId] === name);
4749
4795
  if (!attr) return void 0;
4750
4796
  const ci = attr.compiledItem;
4751
4797
  if (ci?.str?.value !== void 0) return ci.str.value;
4752
4798
  if (ci?.ref?.name !== void 0) return ci.ref.name;
4753
- return attr.value || void 0;
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;
4754
4805
  }
4755
- function getBoolAttr(attrs, resId, defaultVal) {
4756
- const val = getAttrValue(attrs, resId);
4806
+ function getBoolByName(attrs, name, defaultVal) {
4807
+ const val = getAttrByName(attrs, name);
4757
4808
  if (val === void 0) return defaultVal;
4758
4809
  return val === "true" || val === "1";
4759
4810
  }
4760
- function getIntAttr(attrs, resId, defaultVal) {
4761
- const val = getAttrValue(attrs, resId);
4811
+ function getIntByName(attrs, name, defaultVal) {
4812
+ const val = getAttrByName(attrs, name);
4762
4813
  if (val === void 0) return defaultVal;
4763
4814
  const n = parseInt(val, 10);
4764
4815
  return isNaN(n) ? defaultVal : n;
@@ -4769,23 +4820,25 @@ function getChildren(elem, tagName) {
4769
4820
  function extractManifestData(manifest) {
4770
4821
  const attrs = manifest.attribute || [];
4771
4822
  const packageName = getAttrByName(attrs, "package") || "";
4772
- const versionCode = getIntAttr(attrs, 16842767, 0);
4773
- const versionName = getAttrValue(attrs, 16842768) || "";
4823
+ const versionCodeStr = getAttrByName(attrs, "versionCode");
4824
+ const versionCode = versionCodeStr ? parseInt(versionCodeStr, 10) || 0 : 0;
4825
+ const versionName = getAttrByName(attrs, "versionName") || "";
4774
4826
  const usesSdkElements = getChildren(manifest, "uses-sdk");
4775
4827
  const usesSdk = usesSdkElements[0];
4776
- const minSdk = usesSdk ? getIntAttr(usesSdk.attribute || [], 16843276, 1) : 1;
4777
- const targetSdk = usesSdk ? getIntAttr(usesSdk.attribute || [], 16843376, minSdk) : minSdk;
4778
- const permissions = getChildren(manifest, "uses-permission").map((el) => getAttrValue(el.attribute || [], 16842755)).filter((p) => p !== void 0);
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);
4779
4831
  const features = getChildren(manifest, "uses-feature").map((el) => ({
4780
- name: getAttrValue(el.attribute || [], 16842755) || "",
4781
- required: getBoolAttr(el.attribute || [], 16843402, true)
4832
+ name: getAttrByName(el.attribute || [], "name") || "",
4833
+ required: getBoolByName(el.attribute || [], "required", true)
4782
4834
  }));
4783
4835
  const appElements = getChildren(manifest, "application");
4784
4836
  const app = appElements[0];
4785
- const debuggable = app ? getBoolAttr(app.attribute || [], 16842782, false) : false;
4786
- const testOnly = getBoolAttr(attrs, 16843378, false);
4787
- const usesCleartextTraffic = app ? getBoolAttr(app.attribute || [], 16843760, true) : true;
4788
- const extractNativeLibs = app ? getBoolAttr(app.attribute || [], 16844010, true) : true;
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;
4789
4842
  const activities = app ? extractComponents(app, "activity") : [];
4790
4843
  const services = app ? extractComponents(app, "service") : [];
4791
4844
  const receivers = app ? extractComponents(app, "receiver") : [];
@@ -4800,6 +4853,7 @@ function extractManifestData(manifest) {
4800
4853
  testOnly,
4801
4854
  usesCleartextTraffic,
4802
4855
  extractNativeLibs,
4856
+ pageSizeCompat: pageSizeCompat || void 0,
4803
4857
  permissions,
4804
4858
  features,
4805
4859
  activities,
@@ -4811,13 +4865,29 @@ function extractManifestData(manifest) {
4811
4865
  function extractComponents(appElement, tagName) {
4812
4866
  return getChildren(appElement, tagName).map((el) => {
4813
4867
  const compAttrs = el.attribute || [];
4814
- const exportedVal = getAttrValue(compAttrs, 16842795);
4815
- const hasIntentFilter = getChildren(el, "intent-filter").length > 0;
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
+ }
4816
4883
  return {
4817
- name: getAttrValue(compAttrs, 16842755) || "",
4884
+ name: getAttrByName(compAttrs, "name") || "",
4818
4885
  exported: exportedVal === void 0 ? void 0 : exportedVal === "true" || exportedVal === "1",
4819
- foregroundServiceType: tagName === "service" ? getAttrValue(compAttrs, 16843985) : void 0,
4820
- hasIntentFilter
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
4821
4891
  };
4822
4892
  });
4823
4893
  }
@@ -4825,12 +4895,14 @@ function extractComponents(appElement, tagName) {
4825
4895
  // src/preflight/aab-reader.ts
4826
4896
  var AAB_MANIFEST_PATH = "base/manifest/AndroidManifest.xml";
4827
4897
  var APK_MANIFEST_PATH = "AndroidManifest.xml";
4898
+ var SO_HEADER_BYTES = 256;
4899
+ var SO_PATH_RE = /\/lib\/[^/]+\/[^/]+\.so$/;
4828
4900
  function detectManifestPath(filePath) {
4829
4901
  return filePath.toLowerCase().endsWith(".apk") ? APK_MANIFEST_PATH : AAB_MANIFEST_PATH;
4830
4902
  }
4831
4903
  async function readAab(aabPath) {
4832
4904
  const manifestPath = detectManifestPath(aabPath);
4833
- const { zipfile, entries, manifestBuf } = await openAndScan(aabPath, manifestPath);
4905
+ const { zipfile, entries, manifestBuf, soHeaders } = await openAndScan(aabPath, manifestPath);
4834
4906
  zipfile.close();
4835
4907
  if (!manifestBuf) {
4836
4908
  const fileType = aabPath.toLowerCase().endsWith(".apk") ? "APK" : "AAB";
@@ -4846,7 +4918,7 @@ async function readAab(aabPath) {
4846
4918
  manifest = createFallbackManifest();
4847
4919
  manifest._parseError = `Manifest could not be fully parsed: ${errMsg}. Manifest-dependent checks will be skipped.`;
4848
4920
  }
4849
- return { manifest, entries };
4921
+ return { manifest, entries, nativeLibHeaders: soHeaders };
4850
4922
  }
4851
4923
  function createFallbackManifest() {
4852
4924
  return {
@@ -4875,8 +4947,11 @@ function openAndScan(aabPath, manifestPath = AAB_MANIFEST_PATH) {
4875
4947
  return;
4876
4948
  }
4877
4949
  const entries = [];
4950
+ const soHeaders = /* @__PURE__ */ new Map();
4878
4951
  let manifestBuf = null;
4879
4952
  let rejected = false;
4953
+ let pendingStreams = 0;
4954
+ let entrysDone = false;
4880
4955
  function fail(error) {
4881
4956
  if (!rejected) {
4882
4957
  rejected = true;
@@ -4884,6 +4959,46 @@ function openAndScan(aabPath, manifestPath = AAB_MANIFEST_PATH) {
4884
4959
  reject(error);
4885
4960
  }
4886
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
+ }
4887
5002
  zipfile.on("error", fail);
4888
5003
  zipfile.on("entry", (entry) => {
4889
5004
  if (rejected) return;
@@ -4896,27 +5011,23 @@ function openAndScan(aabPath, manifestPath = AAB_MANIFEST_PATH) {
4896
5011
  });
4897
5012
  }
4898
5013
  if (path === manifestPath) {
4899
- zipfile.openReadStream(entry, (streamErr, stream) => {
4900
- if (streamErr || !stream) {
4901
- fail(streamErr ?? new Error("Failed to read manifest entry"));
4902
- return;
4903
- }
4904
- const chunks = [];
4905
- stream.on("data", (chunk) => chunks.push(chunk));
4906
- stream.on("error", (e) => fail(e));
4907
- stream.on("end", () => {
4908
- manifestBuf = Buffer.concat(chunks);
4909
- zipfile.readEntry();
4910
- });
5014
+ readEntryStream(entry, (buf) => {
5015
+ manifestBuf = buf;
4911
5016
  });
4912
- } else {
4913
- zipfile.readEntry();
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
+ );
4914
5025
  }
5026
+ zipfile.readEntry();
4915
5027
  });
4916
5028
  zipfile.on("end", () => {
4917
- if (!rejected) {
4918
- resolve2({ zipfile, entries, manifestBuf });
4919
- }
5029
+ entrysDone = true;
5030
+ maybeResolve();
4920
5031
  });
4921
5032
  zipfile.readEntry();
4922
5033
  });
@@ -4985,13 +5096,13 @@ var manifestScanner = {
4985
5096
  });
4986
5097
  }
4987
5098
  if (manifest.targetSdk >= 31) {
4988
- const allComponents = [
5099
+ const allComponents2 = [
4989
5100
  ...manifest.activities,
4990
5101
  ...manifest.services,
4991
5102
  ...manifest.receivers,
4992
5103
  ...manifest.providers
4993
5104
  ];
4994
- for (const comp of allComponents) {
5105
+ for (const comp of allComponents2) {
4995
5106
  if (comp.hasIntentFilter && comp.exported === void 0) {
4996
5107
  findings.push({
4997
5108
  scanner: "manifest",
@@ -5023,6 +5134,27 @@ var manifestScanner = {
5023
5134
  }
5024
5135
  }
5025
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
+ }
5026
5158
  if (manifest.minSdk < 21) {
5027
5159
  findings.push({
5028
5160
  scanner: "manifest",
@@ -5253,9 +5385,53 @@ var permissionsScanner = {
5253
5385
  // src/preflight/scanners/native-libs-scanner.ts
5254
5386
  var KNOWN_ABIS = ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"];
5255
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
+ }
5256
5432
  var nativeLibsScanner = {
5257
5433
  name: "native-libs",
5258
- description: "Checks native library architectures for 64-bit compliance",
5434
+ description: "Checks native library architectures, 64-bit compliance, and 16KB alignment",
5259
5435
  requires: ["zipEntries"],
5260
5436
  async scan(ctx) {
5261
5437
  const entries = ctx.zipEntries;
@@ -5298,6 +5474,7 @@ var nativeLibsScanner = {
5298
5474
  policyUrl: "https://developer.android.com/google/play/requirements/64-bit"
5299
5475
  });
5300
5476
  }
5477
+ check16KBAlignment(ctx.nativeLibHeaders, ctx.manifest, findings);
5301
5478
  const detectedAbis = KNOWN_ABIS.filter((abi) => abisFound.has(abi));
5302
5479
  const unknownAbis = [...abisFound].filter(
5303
5480
  (abi) => !KNOWN_ABIS.includes(abi)
@@ -5324,6 +5501,33 @@ var nativeLibsScanner = {
5324
5501
  return findings;
5325
5502
  }
5326
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
+ }
5327
5531
 
5328
5532
  // src/preflight/scanners/metadata-scanner.ts
5329
5533
  import { readdir as readdir5, stat as stat8, readFile as readFile10 } from "fs/promises";
@@ -6021,6 +6225,7 @@ async function runPreflight(options) {
6021
6225
  const aab = await readAab(options.aabPath);
6022
6226
  ctx.manifest = aab.manifest;
6023
6227
  ctx.zipEntries = aab.entries;
6228
+ ctx.nativeLibHeaders = aab.nativeLibHeaders;
6024
6229
  if (aab.manifest._parseError) {
6025
6230
  earlyFindings.push({
6026
6231
  scanner: "manifest-parser",
@@ -6431,68 +6636,6 @@ async function downloadGeneratedApk(client, packageName, versionCode, apkId, out
6431
6636
  return { path: outputPath, sizeBytes: bytes.byteLength };
6432
6637
  }
6433
6638
 
6434
- // src/commands/purchase-options.ts
6435
- async function listPurchaseOptions(client, packageName) {
6436
- try {
6437
- return await client.purchaseOptions.list(packageName);
6438
- } catch (error) {
6439
- throw new GpcError(
6440
- `Failed to list purchase options: ${error instanceof Error ? error.message : String(error)}`,
6441
- "PURCHASE_OPTIONS_LIST_FAILED",
6442
- 4,
6443
- "Check your package name and API permissions."
6444
- );
6445
- }
6446
- }
6447
- async function getPurchaseOption(client, packageName, purchaseOptionId) {
6448
- try {
6449
- return await client.purchaseOptions.get(packageName, purchaseOptionId);
6450
- } catch (error) {
6451
- throw new GpcError(
6452
- `Failed to get purchase option "${purchaseOptionId}": ${error instanceof Error ? error.message : String(error)}`,
6453
- "PURCHASE_OPTION_GET_FAILED",
6454
- 4,
6455
- "Check that the purchase option ID exists."
6456
- );
6457
- }
6458
- }
6459
- async function createPurchaseOption(client, packageName, data) {
6460
- try {
6461
- return await client.purchaseOptions.create(packageName, data);
6462
- } catch (error) {
6463
- throw new GpcError(
6464
- `Failed to create purchase option: ${error instanceof Error ? error.message : String(error)}`,
6465
- "PURCHASE_OPTION_CREATE_FAILED",
6466
- 4,
6467
- "Check your purchase option data and API permissions."
6468
- );
6469
- }
6470
- }
6471
- async function activatePurchaseOption(client, packageName, purchaseOptionId) {
6472
- try {
6473
- return await client.purchaseOptions.activate(packageName, purchaseOptionId);
6474
- } catch (error) {
6475
- throw new GpcError(
6476
- `Failed to activate purchase option "${purchaseOptionId}": ${error instanceof Error ? error.message : String(error)}`,
6477
- "PURCHASE_OPTION_ACTIVATE_FAILED",
6478
- 4,
6479
- "Check that the purchase option exists and is in a valid state for activation."
6480
- );
6481
- }
6482
- }
6483
- async function deactivatePurchaseOption(client, packageName, purchaseOptionId) {
6484
- try {
6485
- return await client.purchaseOptions.deactivate(packageName, purchaseOptionId);
6486
- } catch (error) {
6487
- throw new GpcError(
6488
- `Failed to deactivate purchase option "${purchaseOptionId}": ${error instanceof Error ? error.message : String(error)}`,
6489
- "PURCHASE_OPTION_DEACTIVATE_FAILED",
6490
- 4,
6491
- "Check that the purchase option exists and is in a valid state for deactivation."
6492
- );
6493
- }
6494
- }
6495
-
6496
6639
  // src/commands/bundle-analysis.ts
6497
6640
  import { readFile as readFile14, stat as stat10 } from "fs/promises";
6498
6641
  var EOCD_SIGNATURE = 101010256;
@@ -7177,21 +7320,15 @@ async function fetchChangelog(options) {
7177
7320
  signal: controller.signal
7178
7321
  });
7179
7322
  } catch {
7180
- throw new Error(
7181
- `Could not fetch changelog. View online: ${DOCS_CHANGELOG_URL}`
7182
- );
7323
+ throw new Error(`Could not fetch changelog. View online: ${DOCS_CHANGELOG_URL}`);
7183
7324
  } finally {
7184
7325
  clearTimeout(timer);
7185
7326
  }
7186
7327
  if (!response.ok) {
7187
7328
  if (response.status === 404 && options?.version) {
7188
- throw new Error(
7189
- `Version ${options.version} not found. Run: gpc changelog --limit 10`
7190
- );
7329
+ throw new Error(`Version ${options.version} not found. Run: gpc changelog --limit 10`);
7191
7330
  }
7192
- throw new Error(
7193
- `GitHub API returned ${response.status}. View online: ${DOCS_CHANGELOG_URL}`
7194
- );
7331
+ throw new Error(`GitHub API returned ${response.status}. View online: ${DOCS_CHANGELOG_URL}`);
7195
7332
  }
7196
7333
  const data = await response.json();
7197
7334
  const releases = Array.isArray(data) ? data : [data];
@@ -7240,7 +7377,7 @@ async function getRtdnStatus(client, packageName) {
7240
7377
  try {
7241
7378
  const edit = await client.edits.insert(packageName);
7242
7379
  try {
7243
- const details = await client.details.get(packageName, edit.id);
7380
+ await client.details.get(packageName, edit.id);
7244
7381
  return {
7245
7382
  topicName: null,
7246
7383
  enabled: false
@@ -7324,7 +7461,6 @@ export {
7324
7461
  acknowledgeProductPurchase,
7325
7462
  activateBasePlan,
7326
7463
  activateOffer,
7327
- activatePurchaseOption,
7328
7464
  addRecoveryTargeting,
7329
7465
  addTesters,
7330
7466
  advanceTrain,
@@ -7354,14 +7490,12 @@ export {
7354
7490
  createOffer,
7355
7491
  createOneTimeOffer,
7356
7492
  createOneTimeProduct,
7357
- createPurchaseOption,
7358
7493
  createRecoveryAction,
7359
7494
  createSpinner,
7360
7495
  createSubscription,
7361
7496
  createTrack,
7362
7497
  deactivateBasePlan,
7363
7498
  deactivateOffer,
7364
- deactivatePurchaseOption,
7365
7499
  decodeNotification,
7366
7500
  deferSubscriptionPurchase,
7367
7501
  deferSubscriptionV2,
@@ -7419,7 +7553,6 @@ export {
7419
7553
  getOrderDetails,
7420
7554
  getProductPurchase,
7421
7555
  getProductPurchaseV2,
7422
- getPurchaseOption,
7423
7556
  getQuotaUsage,
7424
7557
  getReleasesStatus,
7425
7558
  getReview,
@@ -7464,7 +7597,6 @@ export {
7464
7597
  listOffers,
7465
7598
  listOneTimeOffers,
7466
7599
  listOneTimeProducts,
7467
- listPurchaseOptions,
7468
7600
  listRecoveryActions,
7469
7601
  listReports,
7470
7602
  listReviews,
@@ -7492,7 +7624,6 @@ export {
7492
7624
  redactSensitive,
7493
7625
  refundExternalTransaction,
7494
7626
  refundOrder,
7495
- refundSubscriptionV2,
7496
7627
  relativeTime,
7497
7628
  removeTesters,
7498
7629
  removeUser,