@gpc-cli/core 0.9.72 → 0.9.74
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 +155 -114
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4659,19 +4659,12 @@ async function diffAchievementConfig(client, achievementId, localData) {
|
|
|
4659
4659
|
validateResourceId(achievementId, "achievement");
|
|
4660
4660
|
const remote = await client.achievements.get(achievementId);
|
|
4661
4661
|
const diffs = [];
|
|
4662
|
-
const fields = [
|
|
4663
|
-
"achievementType",
|
|
4664
|
-
"initialState",
|
|
4665
|
-
"stepsToUnlock",
|
|
4666
|
-
"draft"
|
|
4667
|
-
];
|
|
4662
|
+
const fields = ["achievementType", "initialState", "stepsToUnlock", "draft"];
|
|
4668
4663
|
for (const field of fields) {
|
|
4669
4664
|
const localVal = JSON.stringify(
|
|
4670
4665
|
localData[field] ?? null
|
|
4671
4666
|
);
|
|
4672
|
-
const remoteVal = JSON.stringify(
|
|
4673
|
-
remote[field] ?? null
|
|
4674
|
-
);
|
|
4667
|
+
const remoteVal = JSON.stringify(remote[field] ?? null);
|
|
4675
4668
|
if (localVal !== remoteVal) {
|
|
4676
4669
|
diffs.push({ field, local: localVal, remote: remoteVal });
|
|
4677
4670
|
}
|
|
@@ -4707,9 +4700,7 @@ async function diffLeaderboardConfig(client, leaderboardId, localData) {
|
|
|
4707
4700
|
const localVal = JSON.stringify(
|
|
4708
4701
|
localData[field] ?? null
|
|
4709
4702
|
);
|
|
4710
|
-
const remoteVal = JSON.stringify(
|
|
4711
|
-
remote[field] ?? null
|
|
4712
|
-
);
|
|
4703
|
+
const remoteVal = JSON.stringify(remote[field] ?? null);
|
|
4713
4704
|
if (localVal !== remoteVal) {
|
|
4714
4705
|
diffs.push({ field, local: localVal, remote: remoteVal });
|
|
4715
4706
|
}
|
|
@@ -4964,7 +4955,7 @@ async function initProject(options) {
|
|
|
4964
4955
|
const preflightrc = JSON.stringify(
|
|
4965
4956
|
{
|
|
4966
4957
|
failOn: "error",
|
|
4967
|
-
targetSdkMinimum:
|
|
4958
|
+
targetSdkMinimum: 36,
|
|
4968
4959
|
maxDownloadSizeMb: 150,
|
|
4969
4960
|
allowedPermissions: [],
|
|
4970
4961
|
disabledRules: [],
|
|
@@ -5141,7 +5132,8 @@ async function loadPreflightConfig(configPath) {
|
|
|
5141
5132
|
}
|
|
5142
5133
|
|
|
5143
5134
|
// src/preflight/aab-reader.ts
|
|
5144
|
-
import {
|
|
5135
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
5136
|
+
import { fromBuffer } from "yauzl";
|
|
5145
5137
|
|
|
5146
5138
|
// src/preflight/manifest-parser.ts
|
|
5147
5139
|
import protobuf from "protobufjs";
|
|
@@ -5330,12 +5322,20 @@ var AAB_MANIFEST_PATH = "base/manifest/AndroidManifest.xml";
|
|
|
5330
5322
|
var APK_MANIFEST_PATH = "AndroidManifest.xml";
|
|
5331
5323
|
var SO_HEADER_BYTES = 4096;
|
|
5332
5324
|
var SO_PATH_RE = /(?:^|\/)lib\/[^/]+\/[^/]+\.so$/;
|
|
5325
|
+
var SCAN_TIMEOUT_MS = 6e4;
|
|
5333
5326
|
function detectManifestPath(filePath) {
|
|
5334
5327
|
return filePath.toLowerCase().endsWith(".apk") ? APK_MANIFEST_PATH : AAB_MANIFEST_PATH;
|
|
5335
5328
|
}
|
|
5336
5329
|
async function readAab(aabPath) {
|
|
5337
5330
|
const manifestPath = detectManifestPath(aabPath);
|
|
5338
|
-
|
|
5331
|
+
let buffer;
|
|
5332
|
+
try {
|
|
5333
|
+
buffer = await readFile10(aabPath);
|
|
5334
|
+
} catch (err) {
|
|
5335
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5336
|
+
throw new Error(`Could not read ${aabPath}: ${msg}`);
|
|
5337
|
+
}
|
|
5338
|
+
const { zipfile, entries, manifestBuf, soHeaders } = await openAndScan(buffer, manifestPath);
|
|
5339
5339
|
zipfile.close();
|
|
5340
5340
|
if (!manifestBuf) {
|
|
5341
5341
|
const fileType = aabPath.toLowerCase().endsWith(".apk") ? "APK" : "AAB";
|
|
@@ -5372,98 +5372,125 @@ function createFallbackManifest() {
|
|
|
5372
5372
|
providers: []
|
|
5373
5373
|
};
|
|
5374
5374
|
}
|
|
5375
|
-
function openAndScan(
|
|
5375
|
+
function openAndScan(buffer, manifestPath = AAB_MANIFEST_PATH) {
|
|
5376
5376
|
return new Promise((resolve2, reject) => {
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5377
|
+
let settled = false;
|
|
5378
|
+
const timer = setTimeout(() => {
|
|
5379
|
+
if (!settled) {
|
|
5380
|
+
settled = true;
|
|
5381
|
+
reject(new Error("Timed out reading the archive. The file may be corrupt or truncated."));
|
|
5381
5382
|
}
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5383
|
+
}, SCAN_TIMEOUT_MS);
|
|
5384
|
+
function settleResolve(value) {
|
|
5385
|
+
if (settled) return;
|
|
5386
|
+
settled = true;
|
|
5387
|
+
clearTimeout(timer);
|
|
5388
|
+
resolve2(value);
|
|
5389
|
+
}
|
|
5390
|
+
function settleReject(error) {
|
|
5391
|
+
if (settled) return;
|
|
5392
|
+
settled = true;
|
|
5393
|
+
clearTimeout(timer);
|
|
5394
|
+
reject(error);
|
|
5395
|
+
}
|
|
5396
|
+
try {
|
|
5397
|
+
fromBuffer(buffer, { lazyEntries: true }, (err, zipfile) => {
|
|
5398
|
+
if (err || !zipfile) {
|
|
5399
|
+
settleReject(err ?? new Error("Failed to open AAB file"));
|
|
5400
|
+
return;
|
|
5393
5401
|
}
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5402
|
+
const entries = [];
|
|
5403
|
+
const soHeaders = /* @__PURE__ */ new Map();
|
|
5404
|
+
let manifestBuf = null;
|
|
5405
|
+
let pendingStreams = 0;
|
|
5406
|
+
let entrysDone = false;
|
|
5407
|
+
function fail(error) {
|
|
5408
|
+
if (settled) return;
|
|
5409
|
+
try {
|
|
5410
|
+
zipfile.close();
|
|
5411
|
+
} catch {
|
|
5412
|
+
}
|
|
5413
|
+
settleReject(error);
|
|
5398
5414
|
}
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
zipfile.openReadStream(entry, (streamErr, stream) => {
|
|
5403
|
-
if (streamErr || !stream) {
|
|
5404
|
-
fail(streamErr ?? new Error(`Failed to read ${entry.fileName}`));
|
|
5405
|
-
return;
|
|
5415
|
+
function maybeResolve() {
|
|
5416
|
+
if (!settled && entrysDone && pendingStreams === 0) {
|
|
5417
|
+
settleResolve({ zipfile, entries, manifestBuf, soHeaders });
|
|
5406
5418
|
}
|
|
5407
|
-
const chunks = [];
|
|
5408
|
-
let totalBytes = 0;
|
|
5409
|
-
let done = false;
|
|
5410
|
-
stream.on("data", (chunk) => {
|
|
5411
|
-
chunks.push(chunk);
|
|
5412
|
-
totalBytes += chunk.length;
|
|
5413
|
-
if (maxBytes && totalBytes >= maxBytes) {
|
|
5414
|
-
stream.destroy();
|
|
5415
|
-
}
|
|
5416
|
-
});
|
|
5417
|
-
stream.on("error", (e) => {
|
|
5418
|
-
if (maxBytes && totalBytes >= maxBytes) return;
|
|
5419
|
-
fail(e);
|
|
5420
|
-
});
|
|
5421
|
-
const finish = () => {
|
|
5422
|
-
if (done) return;
|
|
5423
|
-
done = true;
|
|
5424
|
-
const buf = Buffer.concat(chunks);
|
|
5425
|
-
onData(maxBytes ? buf.subarray(0, maxBytes) : buf);
|
|
5426
|
-
pendingStreams--;
|
|
5427
|
-
maybeResolve();
|
|
5428
|
-
};
|
|
5429
|
-
stream.on("end", finish);
|
|
5430
|
-
stream.on("close", () => {
|
|
5431
|
-
if (totalBytes > 0 && maxBytes) finish();
|
|
5432
|
-
});
|
|
5433
|
-
});
|
|
5434
|
-
}
|
|
5435
|
-
zipfile.on("error", fail);
|
|
5436
|
-
zipfile.on("entry", (entry) => {
|
|
5437
|
-
if (rejected) return;
|
|
5438
|
-
const path = entry.fileName;
|
|
5439
|
-
if (!path.endsWith("/")) {
|
|
5440
|
-
entries.push({
|
|
5441
|
-
path,
|
|
5442
|
-
compressedSize: entry.compressedSize,
|
|
5443
|
-
uncompressedSize: entry.uncompressedSize
|
|
5444
|
-
});
|
|
5445
5419
|
}
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5420
|
+
function readEntryStream(entry, onData, maxBytes) {
|
|
5421
|
+
pendingStreams++;
|
|
5422
|
+
zipfile.openReadStream(entry, (streamErr, stream) => {
|
|
5423
|
+
if (streamErr || !stream) {
|
|
5424
|
+
fail(streamErr ?? new Error(`Failed to read ${entry.fileName}`));
|
|
5425
|
+
return;
|
|
5426
|
+
}
|
|
5427
|
+
const chunks = [];
|
|
5428
|
+
let totalBytes = 0;
|
|
5429
|
+
let done = false;
|
|
5430
|
+
stream.on("data", (chunk) => {
|
|
5431
|
+
chunks.push(chunk);
|
|
5432
|
+
totalBytes += chunk.length;
|
|
5433
|
+
if (maxBytes && totalBytes >= maxBytes) {
|
|
5434
|
+
stream.destroy();
|
|
5435
|
+
}
|
|
5436
|
+
});
|
|
5437
|
+
stream.on("error", (e) => {
|
|
5438
|
+
if (maxBytes && totalBytes >= maxBytes) return;
|
|
5439
|
+
fail(e);
|
|
5440
|
+
});
|
|
5441
|
+
const finish = () => {
|
|
5442
|
+
if (done) return;
|
|
5443
|
+
done = true;
|
|
5444
|
+
const buf = Buffer.concat(chunks);
|
|
5445
|
+
onData(maxBytes ? buf.subarray(0, maxBytes) : buf);
|
|
5446
|
+
pendingStreams--;
|
|
5447
|
+
maybeResolve();
|
|
5448
|
+
};
|
|
5449
|
+
stream.on("end", finish);
|
|
5450
|
+
stream.on("close", () => {
|
|
5451
|
+
if (totalBytes > 0 && maxBytes) finish();
|
|
5452
|
+
});
|
|
5449
5453
|
});
|
|
5450
|
-
} else if (SO_PATH_RE.test(path)) {
|
|
5451
|
-
readEntryStream(
|
|
5452
|
-
entry,
|
|
5453
|
-
(buf) => {
|
|
5454
|
-
soHeaders.set(path, buf);
|
|
5455
|
-
},
|
|
5456
|
-
SO_HEADER_BYTES
|
|
5457
|
-
);
|
|
5458
5454
|
}
|
|
5455
|
+
zipfile.on("error", fail);
|
|
5456
|
+
zipfile.on("entry", (entry) => {
|
|
5457
|
+
if (settled) return;
|
|
5458
|
+
try {
|
|
5459
|
+
const path = entry.fileName;
|
|
5460
|
+
if (!path.endsWith("/")) {
|
|
5461
|
+
entries.push({
|
|
5462
|
+
path,
|
|
5463
|
+
compressedSize: entry.compressedSize,
|
|
5464
|
+
uncompressedSize: entry.uncompressedSize
|
|
5465
|
+
});
|
|
5466
|
+
}
|
|
5467
|
+
if (path === manifestPath) {
|
|
5468
|
+
readEntryStream(entry, (buf) => {
|
|
5469
|
+
manifestBuf = buf;
|
|
5470
|
+
});
|
|
5471
|
+
} else if (SO_PATH_RE.test(path)) {
|
|
5472
|
+
readEntryStream(
|
|
5473
|
+
entry,
|
|
5474
|
+
(buf) => {
|
|
5475
|
+
soHeaders.set(path, buf);
|
|
5476
|
+
},
|
|
5477
|
+
SO_HEADER_BYTES
|
|
5478
|
+
);
|
|
5479
|
+
}
|
|
5480
|
+
zipfile.readEntry();
|
|
5481
|
+
} catch (e) {
|
|
5482
|
+
fail(e instanceof Error ? e : new Error(String(e)));
|
|
5483
|
+
}
|
|
5484
|
+
});
|
|
5485
|
+
zipfile.on("end", () => {
|
|
5486
|
+
entrysDone = true;
|
|
5487
|
+
maybeResolve();
|
|
5488
|
+
});
|
|
5459
5489
|
zipfile.readEntry();
|
|
5460
5490
|
});
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
});
|
|
5465
|
-
zipfile.readEntry();
|
|
5466
|
-
});
|
|
5491
|
+
} catch (err) {
|
|
5492
|
+
settleReject(err instanceof Error ? err : new Error(String(err)));
|
|
5493
|
+
}
|
|
5467
5494
|
});
|
|
5468
5495
|
}
|
|
5469
5496
|
|
|
@@ -5582,8 +5609,8 @@ var manifestScanner = {
|
|
|
5582
5609
|
ruleId: "geofencing-foreground-service",
|
|
5583
5610
|
severity: "warning",
|
|
5584
5611
|
title: `Possible geofencing via foreground service "${service.name}"`,
|
|
5585
|
-
message: `Service "${service.name}" uses foregroundServiceType "location" and the app declares ACCESS_BACKGROUND_LOCATION. Google Play no longer approves geofencing as a foreground service use case (April 2026
|
|
5586
|
-
suggestion: 'If this service performs geofencing, migrate to
|
|
5612
|
+
message: `Service "${service.name}" uses foregroundServiceType "location" and the app declares ACCESS_BACKGROUND_LOCATION. Google Play no longer approves geofencing as a foreground service use case (sensitive-permissions policy announced April 15, 2026); enforcement for apps targeting Android 17 (API 37+) begins October 28, 2026.`,
|
|
5613
|
+
suggestion: 'If this service performs geofencing, migrate to the Geofencing API (LocationServices.getGeofencingClient) or WorkManager/AlarmManager. If this is legitimate background location tracking (navigation, fitness), suppress this rule via .preflightrc.json: "disabledRules": ["geofencing-foreground-service"].',
|
|
5587
5614
|
policyUrl: "https://support.google.com/googleplay/android-developer/answer/16926792"
|
|
5588
5615
|
});
|
|
5589
5616
|
}
|
|
@@ -5812,14 +5839,28 @@ var permissionsScanner = {
|
|
|
5812
5839
|
].filter((p) => manifest.permissions.includes(p) && !allowed.has(p));
|
|
5813
5840
|
if (contactsPerms.length > 0) {
|
|
5814
5841
|
const names = contactsPerms.map((p) => p.split(".").pop()).join(", ");
|
|
5842
|
+
const enforced = manifest.targetSdk >= 37 ? ` Your app targets API ${manifest.targetSdk}, so this is enforced.` : "";
|
|
5815
5843
|
findings.push({
|
|
5816
5844
|
scanner: "permissions",
|
|
5817
5845
|
ruleId: "contacts-permission-broad",
|
|
5818
5846
|
severity: "warning",
|
|
5819
5847
|
title: "Broad contacts access requires migration to Contact Picker",
|
|
5820
|
-
message: `Your app declares ${names}. Google Play
|
|
5821
|
-
suggestion: "Migrate to the Android Contact Picker
|
|
5822
|
-
policyUrl: "https://support.google.com/googleplay/android-developer/answer/
|
|
5848
|
+
message: `Your app declares ${names}. Google Play's Contacts Permissions policy requires the Android Contact Picker (Intent.ACTION_PICK_CONTACTS) unless broad access is core to your app. Mandatory for apps targeting Android 17 (API 37+) on October 28, 2026.${enforced}`,
|
|
5849
|
+
suggestion: "Migrate to the Android Contact Picker (Intent.ACTION_PICK_CONTACTS) for user-initiated contact selection, or file a Permissions Declaration Form before October 28, 2026 if the picker cannot serve your core functionality. Remove READ_CONTACTS/WRITE_CONTACTS unless your app is a dialer, messaging, or contacts management app.",
|
|
5850
|
+
policyUrl: "https://support.google.com/googleplay/android-developer/answer/16935362"
|
|
5851
|
+
});
|
|
5852
|
+
}
|
|
5853
|
+
const fineLocation = "android.permission.ACCESS_FINE_LOCATION";
|
|
5854
|
+
if (manifest.permissions.includes(fineLocation) && !allowed.has(fineLocation)) {
|
|
5855
|
+
const targetsApi37 = manifest.targetSdk >= 37;
|
|
5856
|
+
findings.push({
|
|
5857
|
+
scanner: "permissions",
|
|
5858
|
+
ruleId: "location-minimal-scope",
|
|
5859
|
+
severity: targetsApi37 ? "warning" : "info",
|
|
5860
|
+
title: "Precise location \u2014 consider the location button (minimum scope)",
|
|
5861
|
+
message: `Your app declares ACCESS_FINE_LOCATION. Google Play's Location Permissions policy makes the Android location button the recommended minimum scope for transactional (one-time) precise location. Mandatory for apps targeting Android 17 (API 37+) on October 28, 2026.${targetsApi37 ? ` Your app targets API ${manifest.targetSdk}.` : ""}`,
|
|
5862
|
+
suggestion: 'For one-time/transactional precise location, use the system location button instead of requesting ACCESS_FINE_LOCATION up front. Only keep continuous fine location if navigation, fitness, or a similar feature is core functionality. Suppress via .preflightrc.json "disabledRules": ["location-minimal-scope"] if fine location is essential.',
|
|
5863
|
+
policyUrl: "https://support.google.com/googleplay/android-developer/answer/17033915"
|
|
5823
5864
|
});
|
|
5824
5865
|
}
|
|
5825
5866
|
const broadHealthPerm = "android.permission.health.READ_ALL_HEALTH_DATA";
|
|
@@ -6013,7 +6054,7 @@ ${fileList}` + (hasPageSizeCompat ? "\nandroid:pageSizeCompat is set, so the app
|
|
|
6013
6054
|
}
|
|
6014
6055
|
|
|
6015
6056
|
// src/preflight/scanners/metadata-scanner.ts
|
|
6016
|
-
import { readdir as readdir4, stat as stat6, readFile as
|
|
6057
|
+
import { readdir as readdir4, stat as stat6, readFile as readFile11 } from "fs/promises";
|
|
6017
6058
|
import { join as join8 } from "path";
|
|
6018
6059
|
var SAFE_LANG = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/;
|
|
6019
6060
|
var FILE_MAP2 = {
|
|
@@ -6072,7 +6113,7 @@ var metadataScanner = {
|
|
|
6072
6113
|
for (const [fileName, field] of Object.entries(FILE_MAP2)) {
|
|
6073
6114
|
const filePath = join8(langDir, fileName);
|
|
6074
6115
|
try {
|
|
6075
|
-
const content = await
|
|
6116
|
+
const content = await readFile11(filePath, "utf-8");
|
|
6076
6117
|
fields[field] = content.trimEnd();
|
|
6077
6118
|
} catch {
|
|
6078
6119
|
}
|
|
@@ -6145,7 +6186,7 @@ var metadataScanner = {
|
|
|
6145
6186
|
const defaultLang = locales.includes("en-US") ? "en-US" : locales[0];
|
|
6146
6187
|
const privacyPath = join8(dir, defaultLang, "privacy_policy_url.txt");
|
|
6147
6188
|
try {
|
|
6148
|
-
const url = await
|
|
6189
|
+
const url = await readFile11(privacyPath, "utf-8");
|
|
6149
6190
|
if (!url.trim()) throw new Error("empty");
|
|
6150
6191
|
} catch {
|
|
6151
6192
|
findings.push({
|
|
@@ -6170,7 +6211,7 @@ var metadataScanner = {
|
|
|
6170
6211
|
};
|
|
6171
6212
|
|
|
6172
6213
|
// src/preflight/scanners/secrets-scanner.ts
|
|
6173
|
-
import { readFile as
|
|
6214
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
6174
6215
|
|
|
6175
6216
|
// src/preflight/scan-files.ts
|
|
6176
6217
|
import { readdir as readdir5, stat as stat7 } from "fs/promises";
|
|
@@ -6290,7 +6331,7 @@ var secretsScanner = {
|
|
|
6290
6331
|
for (const filePath of files) {
|
|
6291
6332
|
let content;
|
|
6292
6333
|
try {
|
|
6293
|
-
content = await
|
|
6334
|
+
content = await readFile12(filePath, "utf-8");
|
|
6294
6335
|
} catch {
|
|
6295
6336
|
continue;
|
|
6296
6337
|
}
|
|
@@ -6318,7 +6359,7 @@ var secretsScanner = {
|
|
|
6318
6359
|
};
|
|
6319
6360
|
|
|
6320
6361
|
// src/preflight/scanners/billing-scanner.ts
|
|
6321
|
-
import { readFile as
|
|
6362
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
6322
6363
|
var BILLING_PATTERNS = [
|
|
6323
6364
|
{
|
|
6324
6365
|
ruleId: "billing-stripe-sdk",
|
|
@@ -6379,7 +6420,7 @@ var billingScanner = {
|
|
|
6379
6420
|
for (const filePath of files) {
|
|
6380
6421
|
let content;
|
|
6381
6422
|
try {
|
|
6382
|
-
content = await
|
|
6423
|
+
content = await readFile13(filePath, "utf-8");
|
|
6383
6424
|
} catch {
|
|
6384
6425
|
continue;
|
|
6385
6426
|
}
|
|
@@ -6405,7 +6446,7 @@ var billingScanner = {
|
|
|
6405
6446
|
};
|
|
6406
6447
|
|
|
6407
6448
|
// src/preflight/scanners/privacy-scanner.ts
|
|
6408
|
-
import { readFile as
|
|
6449
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
6409
6450
|
var TRACKING_SDKS = [
|
|
6410
6451
|
{
|
|
6411
6452
|
name: "Facebook SDK",
|
|
@@ -6445,7 +6486,7 @@ var privacyScanner = {
|
|
|
6445
6486
|
for (const filePath of files) {
|
|
6446
6487
|
let content;
|
|
6447
6488
|
try {
|
|
6448
|
-
content = await
|
|
6489
|
+
content = await readFile14(filePath, "utf-8");
|
|
6449
6490
|
} catch {
|
|
6450
6491
|
continue;
|
|
6451
6492
|
}
|
|
@@ -7145,7 +7186,7 @@ async function downloadGeneratedApk(client, packageName, versionCode, apkId, out
|
|
|
7145
7186
|
}
|
|
7146
7187
|
|
|
7147
7188
|
// src/commands/bundle-analysis.ts
|
|
7148
|
-
import { readFile as
|
|
7189
|
+
import { readFile as readFile15, stat as stat8 } from "fs/promises";
|
|
7149
7190
|
var EOCD_SIGNATURE = 101010256;
|
|
7150
7191
|
var CD_SIGNATURE = 33639248;
|
|
7151
7192
|
var MODULE_SUBDIRS = /* @__PURE__ */ new Set(["dex", "manifest", "res", "assets", "lib", "resources.pb", "root"]);
|
|
@@ -7225,7 +7266,7 @@ async function analyzeBundle(filePath) {
|
|
|
7225
7266
|
if (!fileInfo || !fileInfo.isFile()) {
|
|
7226
7267
|
throw new Error(`File not found: ${filePath}`);
|
|
7227
7268
|
}
|
|
7228
|
-
const buf = await
|
|
7269
|
+
const buf = await readFile15(filePath);
|
|
7229
7270
|
const cdEntries = parseCentralDirectory(buf);
|
|
7230
7271
|
const fileType = detectFileType2(filePath);
|
|
7231
7272
|
const isAab = fileType === "aab";
|
|
@@ -7311,7 +7352,7 @@ function topFiles(analysis, n = 20) {
|
|
|
7311
7352
|
async function checkBundleSize(analysis, configPath = ".bundlesize.json") {
|
|
7312
7353
|
let config;
|
|
7313
7354
|
try {
|
|
7314
|
-
const raw = await
|
|
7355
|
+
const raw = await readFile15(configPath, "utf-8");
|
|
7315
7356
|
config = JSON.parse(raw);
|
|
7316
7357
|
} catch {
|
|
7317
7358
|
return { passed: true, violations: [] };
|