@gpc-cli/core 0.9.65 → 0.9.67

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
@@ -10,6 +10,7 @@ import {
10
10
  diffReleases,
11
11
  fetchReleaseNotes,
12
12
  findBundle,
13
+ formatSize,
13
14
  getReleasesStatus,
14
15
  isVersionedNotesDir,
15
16
  listBundles,
@@ -27,7 +28,7 @@ import {
27
28
  validateUploadFile,
28
29
  waitForBundle,
29
30
  waitForBundleProcessing
30
- } from "./chunk-4TBNOCVU.js";
31
+ } from "./chunk-4KDWLQB3.js";
31
32
 
32
33
  // src/output.ts
33
34
  import process2 from "process";
@@ -404,7 +405,7 @@ var PluginManager = class {
404
405
  if (!isTrusted && manifest?.permissions) {
405
406
  validatePermissions(manifest.permissions);
406
407
  }
407
- const hooks = createHooks(
408
+ const allHooks = createHooks(
408
409
  this.beforeHandlers,
409
410
  this.afterHandlers,
410
411
  this.errorHandlers,
@@ -412,7 +413,14 @@ var PluginManager = class {
412
413
  this.afterResponseHandlers,
413
414
  this.registeredCommands
414
415
  );
415
- await plugin.register(hooks);
416
+ const hooks = isTrusted || !manifest?.permissions ? allHooks : createRestrictedHooks(allHooks, new Set(manifest.permissions));
417
+ try {
418
+ await plugin.register(hooks);
419
+ } catch (err) {
420
+ const msg = err instanceof Error ? err.message : String(err);
421
+ console.warn(`[gpc] Plugin "${plugin.name}" failed to register: ${msg}`);
422
+ return;
423
+ }
416
424
  this.plugins.push({
417
425
  name: plugin.name,
418
426
  version: plugin.version,
@@ -508,6 +516,33 @@ function createHooks(beforeHandlers, afterHandlers, errorHandlers, beforeRequest
508
516
  }
509
517
  };
510
518
  }
519
+ var HOOK_PERMISSIONS = {
520
+ beforeCommand: "hooks:beforeCommand",
521
+ afterCommand: "hooks:afterCommand",
522
+ onError: "hooks:onError",
523
+ beforeRequest: "hooks:beforeRequest",
524
+ afterResponse: "hooks:afterResponse",
525
+ registerCommands: "commands:register"
526
+ };
527
+ function createRestrictedHooks(inner, permissions) {
528
+ function gate(key) {
529
+ const requiredPerm = HOOK_PERMISSIONS[key];
530
+ if (permissions.has(requiredPerm)) {
531
+ return inner[key];
532
+ }
533
+ return (() => {
534
+ console.warn(`[gpc] Plugin lacks permission "${requiredPerm}" for hook "${key}"`);
535
+ });
536
+ }
537
+ return {
538
+ beforeCommand: gate("beforeCommand"),
539
+ afterCommand: gate("afterCommand"),
540
+ onError: gate("onError"),
541
+ beforeRequest: gate("beforeRequest"),
542
+ afterResponse: gate("afterResponse"),
543
+ registerCommands: gate("registerCommands")
544
+ };
545
+ }
511
546
  var VALID_PERMISSIONS = /* @__PURE__ */ new Set([
512
547
  "read:config",
513
548
  "write:config",
@@ -730,11 +765,6 @@ async function validateImage(filePath, imageType) {
730
765
  }
731
766
  return { valid: errors.length === 0, errors, warnings };
732
767
  }
733
- function formatSize(bytes) {
734
- if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
735
- if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
736
- return `${bytes} B`;
737
- }
738
768
 
739
769
  // src/utils/fastlane.ts
740
770
  import { readFile, writeFile, mkdir, readdir, stat as stat2 } from "fs/promises";
@@ -2804,7 +2834,10 @@ async function getAppStatus(client, reporting, packageName, options = {}) {
2804
2834
  sections.has("vitals") ? queryVitalWithTrend(reporting, packageName, "slowRenderingRateMetricSet", days) : Promise.resolve(SKIPPED_VITAL),
2805
2835
  sections.has("reviews") ? listReviews(client, packageName, { maxResults: 500 }) : Promise.resolve([])
2806
2836
  ]);
2837
+ const sectionErrors = [];
2807
2838
  const rawReleases = releasesResult.status === "fulfilled" ? releasesResult.value : [];
2839
+ if (releasesResult.status === "rejected")
2840
+ sectionErrors.push(`releases: ${String(releasesResult.reason)}`);
2808
2841
  const releases = rawReleases.map((r) => ({
2809
2842
  track: r.track,
2810
2843
  versionCode: r.versionCodes[r.versionCodes.length - 1] ?? "\u2014",
@@ -2812,10 +2845,20 @@ async function getAppStatus(client, reporting, packageName, options = {}) {
2812
2845
  userFraction: r.userFraction ?? null
2813
2846
  }));
2814
2847
  const crashes = crashesResult.status === "fulfilled" ? crashesResult.value : SKIPPED_VITAL;
2848
+ if (crashesResult.status === "rejected")
2849
+ sectionErrors.push(`vitals/crashes: ${String(crashesResult.reason)}`);
2815
2850
  const anr = anrResult.status === "fulfilled" ? anrResult.value : SKIPPED_VITAL;
2851
+ if (anrResult.status === "rejected")
2852
+ sectionErrors.push(`vitals/anr: ${String(anrResult.reason)}`);
2816
2853
  const slowStart = slowStartResult.status === "fulfilled" ? slowStartResult.value : SKIPPED_VITAL;
2854
+ if (slowStartResult.status === "rejected")
2855
+ sectionErrors.push(`vitals/slowStarts: ${String(slowStartResult.reason)}`);
2817
2856
  const slowRender = slowRenderResult.status === "fulfilled" ? slowRenderResult.value : SKIPPED_VITAL;
2857
+ if (slowRenderResult.status === "rejected")
2858
+ sectionErrors.push(`vitals/slowRender: ${String(slowRenderResult.reason)}`);
2818
2859
  const rawReviews = reviewsResult.status === "fulfilled" ? reviewsResult.value : [];
2860
+ if (reviewsResult.status === "rejected")
2861
+ sectionErrors.push(`reviews: ${String(reviewsResult.reason)}`);
2819
2862
  const reviews = computeReviewSentiment(rawReviews, reviewDays);
2820
2863
  return {
2821
2864
  packageName,
@@ -2845,7 +2888,8 @@ async function getAppStatus(client, reporting, packageName, options = {}) {
2845
2888
  slowRender.trend
2846
2889
  )
2847
2890
  },
2848
- reviews
2891
+ reviews,
2892
+ ...sectionErrors.length > 0 ? { errors: sectionErrors } : {}
2849
2893
  };
2850
2894
  }
2851
2895
  function vitalIndicator(metric) {
@@ -3209,7 +3253,12 @@ async function dispatchWebhook(url, event) {
3209
3253
  signal: AbortSignal.timeout(1e4)
3210
3254
  });
3211
3255
  if (!response.ok) {
3212
- throw new Error(`Webhook returned ${response.status}`);
3256
+ throw new GpcError(
3257
+ `Webhook returned ${response.status}`,
3258
+ "WATCH_WEBHOOK_FAILED",
3259
+ 5,
3260
+ `The webhook endpoint returned HTTP ${response.status}. Check the URL and server status.`
3261
+ );
3213
3262
  }
3214
3263
  }
3215
3264
  async function handleBreach(event, config, client) {
@@ -3226,7 +3275,7 @@ async function handleBreach(event, config, client) {
3226
3275
  }
3227
3276
  case "halt": {
3228
3277
  try {
3229
- const { updateRollout: updateRollout2 } = await import("./releases-XF5MYFQD.js");
3278
+ const { updateRollout: updateRollout2 } = await import("./releases-OAKWPEAC.js");
3230
3279
  await updateRollout2(client, config.packageName, config.track, "halt");
3231
3280
  halted = true;
3232
3281
  } catch {
@@ -3271,8 +3320,13 @@ async function runWatch(client, reporting, config, callbacks) {
3271
3320
  round++;
3272
3321
  const rollout = await fetchRollout(client, config.packageName, config.track);
3273
3322
  const vitals = {};
3274
- for (const metric of config.metrics) {
3275
- const value = await queryWatchMetric(reporting, config.packageName, metric);
3323
+ const metricResults = await Promise.all(
3324
+ config.metrics.map(async (metric) => {
3325
+ const value = await queryWatchMetric(reporting, config.packageName, metric);
3326
+ return { metric, value };
3327
+ })
3328
+ );
3329
+ for (const { metric, value } of metricResults) {
3276
3330
  const threshold = config.thresholds[metric];
3277
3331
  const result = threshold !== void 0 ? checkThreshold(value, threshold) : { breached: false };
3278
3332
  vitals[metric] = {
@@ -3293,14 +3347,12 @@ async function runWatch(client, reporting, config, callbacks) {
3293
3347
  if (breaches.length > 0) {
3294
3348
  breachCount++;
3295
3349
  const stateChanged = await trackBreachState(config.packageName, true);
3296
- if (stateChanged) {
3297
- const didHalt = await handleBreach(event, config, client);
3298
- if (didHalt) {
3299
- event.halted = true;
3300
- halted = true;
3301
- }
3302
- await callbacks.onBreach(event);
3350
+ const didHalt = stateChanged ? await handleBreach(event, config, client) : false;
3351
+ if (didHalt) {
3352
+ event.halted = true;
3353
+ halted = true;
3303
3354
  }
3355
+ await callbacks.onBreach(event);
3304
3356
  callbacks.onEvent(event);
3305
3357
  } else {
3306
3358
  await trackBreachState(config.packageName, false);
@@ -3744,7 +3796,7 @@ async function getUser(client, developerId, email) {
3744
3796
  }
3745
3797
  async function inviteUser(client, developerId, email, permissions, grants) {
3746
3798
  const user = { email };
3747
- if (permissions) user.developerAccountPermission = permissions;
3799
+ if (permissions) user.developerAccountPermissions = permissions;
3748
3800
  if (grants) user.grants = grants;
3749
3801
  return client.create(developerId, user);
3750
3802
  }
@@ -3752,8 +3804,8 @@ async function updateUser(client, developerId, userId, permissions, grants) {
3752
3804
  const updates = {};
3753
3805
  const masks = [];
3754
3806
  if (permissions) {
3755
- updates.developerAccountPermission = permissions;
3756
- masks.push("developerAccountPermission");
3807
+ updates.developerAccountPermissions = permissions;
3808
+ masks.push("developerAccountPermissions");
3757
3809
  }
3758
3810
  if (grants) {
3759
3811
  updates.grants = grants;
@@ -4802,7 +4854,7 @@ jobs:
4802
4854
  run: ./gradlew bundleRelease
4803
4855
 
4804
4856
  - name: Install GPC
4805
- run: npm install -g @gpc-cli/cli
4857
+ run: npm install -g @gpc-cli/cli --ignore-scripts
4806
4858
 
4807
4859
  - name: Preflight compliance check
4808
4860
  run: gpc preflight app/build/outputs/bundle/release/app-release.aab --fail-on error
@@ -4841,14 +4893,14 @@ preflight:
4841
4893
  stage: preflight
4842
4894
  image: node:20
4843
4895
  script:
4844
- - npm install -g @gpc-cli/cli
4896
+ - npm install -g @gpc-cli/cli --ignore-scripts
4845
4897
  - gpc preflight app/build/outputs/bundle/release/app-release.aab --fail-on error --json
4846
4898
 
4847
4899
  release:
4848
4900
  stage: release
4849
4901
  image: node:20
4850
4902
  script:
4851
- - npm install -g @gpc-cli/cli
4903
+ - npm install -g @gpc-cli/cli --ignore-scripts
4852
4904
  - gpc releases upload app/build/outputs/bundle/release/app-release.aab --track internal
4853
4905
  only:
4854
4906
  - tags
@@ -4864,7 +4916,7 @@ var SEVERITY_ORDER = {
4864
4916
  };
4865
4917
  var DEFAULT_PREFLIGHT_CONFIG = {
4866
4918
  failOn: "error",
4867
- targetSdkMinimum: 35,
4919
+ targetSdkMinimum: 36,
4868
4920
  maxDownloadSizeMb: 150,
4869
4921
  allowedPermissions: [],
4870
4922
  disabledRules: [],
@@ -5050,7 +5102,7 @@ function extractManifestData(manifest) {
5050
5102
  const appElements = getChildren(manifest, "application");
5051
5103
  const app = appElements[0];
5052
5104
  const debuggable = app ? getBoolByName(app.attribute || [], "debuggable", false) : false;
5053
- const testOnly = getBoolByName(attrs, "testOnly", false);
5105
+ const testOnly = app ? getBoolByName(app.attribute || [], "testOnly", false) : false;
5054
5106
  const usesCleartextTraffic = app ? getBoolByName(app.attribute || [], "usesCleartextTraffic", true) : true;
5055
5107
  const extractNativeLibs = app ? getBoolByName(app.attribute || [], "extractNativeLibs", true) : true;
5056
5108
  const pageSizeCompat = app ? getBoolByName(app.attribute || [], "pageSizeCompat", false) : false;
@@ -5110,8 +5162,8 @@ function extractComponents(appElement, tagName) {
5110
5162
  // src/preflight/aab-reader.ts
5111
5163
  var AAB_MANIFEST_PATH = "base/manifest/AndroidManifest.xml";
5112
5164
  var APK_MANIFEST_PATH = "AndroidManifest.xml";
5113
- var SO_HEADER_BYTES = 256;
5114
- var SO_PATH_RE = /\/lib\/[^/]+\/[^/]+\.so$/;
5165
+ var SO_HEADER_BYTES = 4096;
5166
+ var SO_PATH_RE = /(?:^|\/)lib\/[^/]+\/[^/]+\.so$/;
5115
5167
  function detectManifestPath(filePath) {
5116
5168
  return filePath.toLowerCase().endsWith(".apk") ? APK_MANIFEST_PATH : AAB_MANIFEST_PATH;
5117
5169
  }
@@ -6506,16 +6558,35 @@ async function runPreflight(options) {
6506
6558
  if (options.metadataDir) ctx.metadataDir = options.metadataDir;
6507
6559
  if (options.sourceDir) ctx.sourceDir = options.sourceDir;
6508
6560
  const requestedNames = options.scanners ? new Set(options.scanners.map((s) => s.toLowerCase())) : null;
6509
- const applicableScanners = ALL_SCANNERS.filter((scanner) => {
6510
- if (requestedNames && !requestedNames.has(scanner.name)) return false;
6561
+ const applicableScanners = [];
6562
+ const skippedScanners = [];
6563
+ for (const scanner of ALL_SCANNERS) {
6564
+ if (requestedNames && !requestedNames.has(scanner.name)) continue;
6565
+ let skip = false;
6511
6566
  for (const req of scanner.requires) {
6512
- if (req === "manifest" && !ctx.manifest) return false;
6513
- if (req === "zipEntries" && !ctx.zipEntries) return false;
6514
- if (req === "metadataDir" && !ctx.metadataDir) return false;
6515
- if (req === "sourceDir" && !ctx.sourceDir) return false;
6567
+ if (req === "manifest" && !ctx.manifest) {
6568
+ skip = true;
6569
+ break;
6570
+ }
6571
+ if (req === "zipEntries" && !ctx.zipEntries) {
6572
+ skip = true;
6573
+ break;
6574
+ }
6575
+ if (req === "metadataDir" && !ctx.metadataDir) {
6576
+ skip = true;
6577
+ break;
6578
+ }
6579
+ if (req === "sourceDir" && !ctx.sourceDir) {
6580
+ skip = true;
6581
+ break;
6582
+ }
6516
6583
  }
6517
- return true;
6518
- });
6584
+ if (skip) {
6585
+ skippedScanners.push(scanner.name);
6586
+ } else {
6587
+ applicableScanners.push(scanner);
6588
+ }
6589
+ }
6519
6590
  const settled = await Promise.allSettled(applicableScanners.map((scanner) => scanner.scan(ctx)));
6520
6591
  let findings = [...earlyFindings];
6521
6592
  for (let i = 0; i < settled.length; i++) {
@@ -6551,6 +6622,7 @@ async function runPreflight(options) {
6551
6622
  const passed = !findings.some((f) => SEVERITY_ORDER[f.severity] >= failThreshold);
6552
6623
  return {
6553
6624
  scanners: applicableScanners.map((s) => s.name),
6625
+ ...skippedScanners.length > 0 ? { skippedScanners } : {},
6554
6626
  findings,
6555
6627
  summary,
6556
6628
  passed,
@@ -6812,12 +6884,17 @@ async function sendWebhook(config, payload, target) {
6812
6884
  try {
6813
6885
  const targets = target ? [target] : Object.keys(FORMATTERS);
6814
6886
  const promises = [];
6887
+ const safePayload = {
6888
+ ...payload,
6889
+ error: payload.error ? String(redactSensitive(payload.error)) : void 0,
6890
+ details: payload.details ? redactSensitive(payload.details) : void 0
6891
+ };
6815
6892
  for (const t of targets) {
6816
6893
  const url = config[t];
6817
6894
  if (!url) continue;
6818
6895
  const formatter = FORMATTERS[t];
6819
6896
  if (!formatter) continue;
6820
- const body = formatter(payload);
6897
+ const body = formatter(safePayload);
6821
6898
  promises.push(sendSingle(url, body).catch(() => {
6822
6899
  }));
6823
6900
  }
@@ -7114,15 +7191,15 @@ async function fetchChangelog(options) {
7114
7191
  signal: controller.signal
7115
7192
  });
7116
7193
  } catch {
7117
- throw new Error(`Could not fetch changelog. View online: ${DOCS_CHANGELOG_URL}`);
7194
+ throw new GpcError(`Could not fetch changelog.`, "CHANGELOG_FETCH_FAILED", 5, `View online: ${DOCS_CHANGELOG_URL}`);
7118
7195
  } finally {
7119
7196
  clearTimeout(timer);
7120
7197
  }
7121
7198
  if (!response.ok) {
7122
7199
  if (response.status === 404 && options?.version) {
7123
- throw new Error(`Version ${options.version} not found. Run: gpc changelog --limit 10`);
7200
+ throw new GpcError(`Version ${options.version} not found.`, "CHANGELOG_VERSION_NOT_FOUND", 1, `Run: gpc changelog --limit 10`);
7124
7201
  }
7125
- throw new Error(`GitHub API returned ${response.status}. View online: ${DOCS_CHANGELOG_URL}`);
7202
+ throw new GpcError(`GitHub API returned ${response.status}.`, "CHANGELOG_FETCH_FAILED", 5, `View online: ${DOCS_CHANGELOG_URL}`);
7126
7203
  }
7127
7204
  const data = await response.json();
7128
7205
  const releases = Array.isArray(data) ? data : [data];