@gpc-cli/core 0.9.59 → 0.9.60
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/{chunk-IZKB6GBS.js → chunk-3AUJEAXP.js} +151 -14
- package/dist/chunk-3AUJEAXP.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +138 -148
- package/dist/index.js.map +1 -1
- package/dist/{releases-VFDJ6IX2.js → releases-S54GLWH3.js} +2 -2
- package/package.json +4 -4
- package/dist/chunk-IZKB6GBS.js.map +0 -1
- /package/dist/{releases-VFDJ6IX2.js.map → releases-S54GLWH3.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -9,16 +9,20 @@ import {
|
|
|
9
9
|
diffReleases,
|
|
10
10
|
fetchReleaseNotes,
|
|
11
11
|
getReleasesStatus,
|
|
12
|
+
isVersionedNotesDir,
|
|
12
13
|
listTracks,
|
|
13
14
|
promoteRelease,
|
|
15
|
+
readReleaseNotesForVersion,
|
|
16
|
+
readReleaseNotesFromDir,
|
|
14
17
|
updateRollout,
|
|
15
18
|
updateTrackConfig,
|
|
16
19
|
uploadExternallyHosted,
|
|
17
20
|
uploadRelease,
|
|
18
21
|
validateAndCommit,
|
|
22
|
+
validateReleaseNotes,
|
|
19
23
|
validateUploadFile,
|
|
20
24
|
waitForBundleProcessing
|
|
21
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-3AUJEAXP.js";
|
|
22
26
|
|
|
23
27
|
// src/output.ts
|
|
24
28
|
import process2 from "process";
|
|
@@ -1151,7 +1155,7 @@ var ALL_IMAGE_TYPES = [
|
|
|
1151
1155
|
];
|
|
1152
1156
|
async function exportImages(client, packageName, dir, options) {
|
|
1153
1157
|
const { mkdir: mkdir8, writeFile: writeFile9 } = await import("fs/promises");
|
|
1154
|
-
const { join:
|
|
1158
|
+
const { join: join12 } = await import("path");
|
|
1155
1159
|
const edit = await client.edits.insert(packageName);
|
|
1156
1160
|
try {
|
|
1157
1161
|
let languages;
|
|
@@ -1182,7 +1186,7 @@ async function exportImages(client, packageName, dir, options) {
|
|
|
1182
1186
|
const batch = tasks.slice(i, i + concurrency);
|
|
1183
1187
|
const results = await Promise.all(
|
|
1184
1188
|
batch.map(async (task) => {
|
|
1185
|
-
const dirPath =
|
|
1189
|
+
const dirPath = join12(dir, task.language, task.imageType);
|
|
1186
1190
|
await mkdir8(dirPath, { recursive: true });
|
|
1187
1191
|
const response = await fetch(task.url);
|
|
1188
1192
|
if (!response.ok) {
|
|
@@ -1194,7 +1198,7 @@ async function exportImages(client, packageName, dir, options) {
|
|
|
1194
1198
|
);
|
|
1195
1199
|
}
|
|
1196
1200
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
1197
|
-
const filePath =
|
|
1201
|
+
const filePath = join12(dirPath, `${task.index}.png`);
|
|
1198
1202
|
await writeFile9(filePath, buffer);
|
|
1199
1203
|
return buffer.length;
|
|
1200
1204
|
})
|
|
@@ -1513,55 +1517,8 @@ function validateSku(sku) {
|
|
|
1513
1517
|
}
|
|
1514
1518
|
}
|
|
1515
1519
|
|
|
1516
|
-
// src/utils/release-notes.ts
|
|
1517
|
-
import { readdir as readdir3, readFile as readFile3, stat as stat3 } from "fs/promises";
|
|
1518
|
-
import { extname as extname2, basename, join as join3 } from "path";
|
|
1519
|
-
var MAX_NOTES_LENGTH = 500;
|
|
1520
|
-
async function readReleaseNotesFromDir(dir) {
|
|
1521
|
-
let entries;
|
|
1522
|
-
try {
|
|
1523
|
-
entries = await readdir3(dir);
|
|
1524
|
-
} catch {
|
|
1525
|
-
throw new GpcError(
|
|
1526
|
-
`Release notes directory not found: ${dir}`,
|
|
1527
|
-
"RELEASE_NOTES_DIR_NOT_FOUND",
|
|
1528
|
-
1,
|
|
1529
|
-
`Create the directory and add .txt files named by language code (e.g., en-US.txt). Path: ${dir}`
|
|
1530
|
-
);
|
|
1531
|
-
}
|
|
1532
|
-
const notes = [];
|
|
1533
|
-
for (const entry of entries) {
|
|
1534
|
-
if (extname2(entry) !== ".txt") continue;
|
|
1535
|
-
const language = basename(entry, ".txt");
|
|
1536
|
-
const filePath = join3(dir, entry);
|
|
1537
|
-
const stats = await stat3(filePath);
|
|
1538
|
-
if (!stats.isFile()) continue;
|
|
1539
|
-
const text = (await readFile3(filePath, "utf-8")).trim();
|
|
1540
|
-
if (text.length === 0) continue;
|
|
1541
|
-
notes.push({ language, text });
|
|
1542
|
-
}
|
|
1543
|
-
return notes;
|
|
1544
|
-
}
|
|
1545
|
-
function validateReleaseNotes(notes) {
|
|
1546
|
-
const errors = [];
|
|
1547
|
-
const warnings = [];
|
|
1548
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1549
|
-
for (const note of notes) {
|
|
1550
|
-
if (seen.has(note.language)) {
|
|
1551
|
-
errors.push(`Duplicate language code: ${note.language}`);
|
|
1552
|
-
}
|
|
1553
|
-
seen.add(note.language);
|
|
1554
|
-
if (note.text.length > MAX_NOTES_LENGTH) {
|
|
1555
|
-
warnings.push(
|
|
1556
|
-
`Release notes for "${note.language}" are ${note.text.length} chars (max ${MAX_NOTES_LENGTH}) \u2014 Google Play will reject notes exceeding this limit`
|
|
1557
|
-
);
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
return { valid: errors.length === 0, errors, warnings };
|
|
1561
|
-
}
|
|
1562
|
-
|
|
1563
1520
|
// src/commands/validate.ts
|
|
1564
|
-
import { stat as
|
|
1521
|
+
import { stat as stat3 } from "fs/promises";
|
|
1565
1522
|
var STANDARD_TRACKS = /* @__PURE__ */ new Set([
|
|
1566
1523
|
"internal",
|
|
1567
1524
|
"qa",
|
|
@@ -1605,7 +1562,7 @@ async function validatePreSubmission(options) {
|
|
|
1605
1562
|
}
|
|
1606
1563
|
if (options.mappingFile) {
|
|
1607
1564
|
try {
|
|
1608
|
-
const stats = await
|
|
1565
|
+
const stats = await stat3(options.mappingFile);
|
|
1609
1566
|
checks.push({
|
|
1610
1567
|
name: "mapping",
|
|
1611
1568
|
passed: stats.isFile(),
|
|
@@ -2404,12 +2361,28 @@ var METRIC_SET_METRICS = {
|
|
|
2404
2361
|
],
|
|
2405
2362
|
errorCountMetricSet: ["errorReportCount", "distinctUsers"]
|
|
2406
2363
|
};
|
|
2407
|
-
function
|
|
2364
|
+
async function getFreshnessEndDate(reporting, packageName, metricSet, aggregation = "DAILY") {
|
|
2365
|
+
try {
|
|
2366
|
+
const info = await reporting.getMetricSetFreshness(packageName, metricSet);
|
|
2367
|
+
const match = info.freshnessInfo?.freshnesses?.find(
|
|
2368
|
+
(f) => f.aggregationPeriod === aggregation
|
|
2369
|
+
);
|
|
2370
|
+
if (match) {
|
|
2371
|
+
return new Date(
|
|
2372
|
+
Date.UTC(match.latestEndTime.year, match.latestEndTime.month - 1, match.latestEndTime.day)
|
|
2373
|
+
);
|
|
2374
|
+
}
|
|
2375
|
+
} catch {
|
|
2376
|
+
}
|
|
2377
|
+
return void 0;
|
|
2378
|
+
}
|
|
2379
|
+
function buildQuery(metricSet, options, freshnessEnd) {
|
|
2408
2380
|
const metrics = METRIC_SET_METRICS[metricSet] ?? ["errorReportCount", "distinctUsers"];
|
|
2409
2381
|
const days = options?.days ?? 30;
|
|
2410
2382
|
const DAY_MS = 24 * 60 * 60 * 1e3;
|
|
2411
|
-
const
|
|
2412
|
-
const
|
|
2383
|
+
const yesterday = new Date(Date.now() - DAY_MS);
|
|
2384
|
+
const end = freshnessEnd && freshnessEnd < yesterday ? freshnessEnd : yesterday;
|
|
2385
|
+
const start = new Date(end.getTime() - days * DAY_MS);
|
|
2413
2386
|
const query = {
|
|
2414
2387
|
metrics,
|
|
2415
2388
|
timelineSpec: {
|
|
@@ -2432,7 +2405,13 @@ function buildQuery(metricSet, options) {
|
|
|
2432
2405
|
return query;
|
|
2433
2406
|
}
|
|
2434
2407
|
async function queryMetric(reporting, packageName, metricSet, options) {
|
|
2435
|
-
const
|
|
2408
|
+
const freshnessEnd = await getFreshnessEndDate(
|
|
2409
|
+
reporting,
|
|
2410
|
+
packageName,
|
|
2411
|
+
metricSet,
|
|
2412
|
+
options?.aggregation
|
|
2413
|
+
);
|
|
2414
|
+
const query = buildQuery(metricSet, options, freshnessEnd);
|
|
2436
2415
|
return reporting.queryMetricSet(packageName, metricSet, query);
|
|
2437
2416
|
}
|
|
2438
2417
|
async function getVitalsOverview(reporting, packageName) {
|
|
@@ -2444,8 +2423,15 @@ async function getVitalsOverview(reporting, packageName) {
|
|
|
2444
2423
|
["excessiveWakeupRateMetricSet", "excessiveWakeupRate"],
|
|
2445
2424
|
["stuckBackgroundWakelockRateMetricSet", "stuckWakelockRate"]
|
|
2446
2425
|
];
|
|
2426
|
+
const freshnessResults = await Promise.allSettled(
|
|
2427
|
+
metricSets.map(([metric]) => getFreshnessEndDate(reporting, packageName, metric))
|
|
2428
|
+
);
|
|
2447
2429
|
const results = await Promise.allSettled(
|
|
2448
|
-
metricSets.map(([metric]) =>
|
|
2430
|
+
metricSets.map(([metric], i) => {
|
|
2431
|
+
const fr = freshnessResults[i];
|
|
2432
|
+
const freshnessEnd = fr?.status === "fulfilled" ? fr.value : void 0;
|
|
2433
|
+
return reporting.queryMetricSet(packageName, metric, buildQuery(metric, void 0, freshnessEnd));
|
|
2434
|
+
})
|
|
2449
2435
|
);
|
|
2450
2436
|
const overview = {};
|
|
2451
2437
|
for (let i = 0; i < metricSets.length; i++) {
|
|
@@ -2498,7 +2484,9 @@ async function searchVitalsErrors(reporting, packageName, options) {
|
|
|
2498
2484
|
async function compareVitalsTrend(reporting, packageName, metricSet, days = 7) {
|
|
2499
2485
|
const DAY_MS = 24 * 60 * 60 * 1e3;
|
|
2500
2486
|
const nowMs = Date.now();
|
|
2501
|
-
const
|
|
2487
|
+
const freshnessEnd = await getFreshnessEndDate(reporting, packageName, metricSet);
|
|
2488
|
+
const fallback = nowMs - 2 * DAY_MS;
|
|
2489
|
+
const baseMs = freshnessEnd ? Math.min(freshnessEnd.getTime(), fallback) : fallback;
|
|
2502
2490
|
const currentEnd = new Date(baseMs);
|
|
2503
2491
|
const currentStart = new Date(baseMs - days * DAY_MS);
|
|
2504
2492
|
const previousEnd = new Date(baseMs - days * DAY_MS - DAY_MS);
|
|
@@ -2644,17 +2632,17 @@ function watchVitalsWithAutoHalt(reporting, packageName, options) {
|
|
|
2644
2632
|
}
|
|
2645
2633
|
|
|
2646
2634
|
// src/commands/status.ts
|
|
2647
|
-
import { mkdir as mkdir3, readFile as
|
|
2635
|
+
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
2648
2636
|
import { execFile } from "child_process";
|
|
2649
|
-
import { join as
|
|
2637
|
+
import { join as join3 } from "path";
|
|
2650
2638
|
import { getCacheDir } from "@gpc-cli/config";
|
|
2651
2639
|
var DEFAULT_TTL_SECONDS = 3600;
|
|
2652
2640
|
function cacheFilePath(packageName) {
|
|
2653
|
-
return
|
|
2641
|
+
return join3(getCacheDir(), `status-${packageName}.json`);
|
|
2654
2642
|
}
|
|
2655
2643
|
async function loadStatusCache(packageName, ttlSeconds = DEFAULT_TTL_SECONDS) {
|
|
2656
2644
|
try {
|
|
2657
|
-
const raw = await
|
|
2645
|
+
const raw = await readFile3(cacheFilePath(packageName), "utf-8");
|
|
2658
2646
|
const entry = JSON.parse(raw);
|
|
2659
2647
|
const age = (Date.now() - new Date(entry.fetchedAt).getTime()) / 1e3;
|
|
2660
2648
|
if (age > (entry.ttl ?? ttlSeconds)) return null;
|
|
@@ -3063,13 +3051,13 @@ async function runWatchLoop(opts) {
|
|
|
3063
3051
|
}
|
|
3064
3052
|
}
|
|
3065
3053
|
function breachStateFilePath(packageName) {
|
|
3066
|
-
return
|
|
3054
|
+
return join3(getCacheDir(), `breach-state-${packageName}.json`);
|
|
3067
3055
|
}
|
|
3068
3056
|
async function trackBreachState(packageName, isBreaching) {
|
|
3069
3057
|
const filePath = breachStateFilePath(packageName);
|
|
3070
3058
|
let prevBreaching = false;
|
|
3071
3059
|
try {
|
|
3072
|
-
const raw = await
|
|
3060
|
+
const raw = await readFile3(filePath, "utf-8");
|
|
3073
3061
|
prevBreaching = JSON.parse(raw).breaching;
|
|
3074
3062
|
} catch {
|
|
3075
3063
|
}
|
|
@@ -3222,7 +3210,7 @@ async function handleBreach(event, config, client) {
|
|
|
3222
3210
|
}
|
|
3223
3211
|
case "halt": {
|
|
3224
3212
|
try {
|
|
3225
|
-
const { updateRollout: updateRollout2 } = await import("./releases-
|
|
3213
|
+
const { updateRollout: updateRollout2 } = await import("./releases-S54GLWH3.js");
|
|
3226
3214
|
await updateRollout2(client, config.packageName, config.track, "halt");
|
|
3227
3215
|
halted = true;
|
|
3228
3216
|
} catch {
|
|
@@ -3338,8 +3326,8 @@ async function runWatch(client, reporting, config, callbacks) {
|
|
|
3338
3326
|
}
|
|
3339
3327
|
|
|
3340
3328
|
// src/commands/iap.ts
|
|
3341
|
-
import { readdir as
|
|
3342
|
-
import { join as
|
|
3329
|
+
import { readdir as readdir3, readFile as readFile4 } from "fs/promises";
|
|
3330
|
+
import { join as join4 } from "path";
|
|
3343
3331
|
import { paginateAll as paginateAll3 } from "@gpc-cli/api";
|
|
3344
3332
|
async function listInAppProducts(client, packageName, options) {
|
|
3345
3333
|
if (options?.limit || options?.nextPage) {
|
|
@@ -3424,11 +3412,11 @@ async function batchUpdateProducts(client, packageName, products) {
|
|
|
3424
3412
|
return results;
|
|
3425
3413
|
}
|
|
3426
3414
|
async function readProductsFromDir(dir) {
|
|
3427
|
-
const files = await
|
|
3415
|
+
const files = await readdir3(dir);
|
|
3428
3416
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
3429
3417
|
const localProducts = [];
|
|
3430
3418
|
for (const file of jsonFiles) {
|
|
3431
|
-
const content = await
|
|
3419
|
+
const content = await readFile4(join4(dir, file), "utf-8");
|
|
3432
3420
|
try {
|
|
3433
3421
|
localProducts.push(JSON.parse(content));
|
|
3434
3422
|
} catch {
|
|
@@ -3785,7 +3773,7 @@ async function deleteGrant(client, developerId, email, packageName) {
|
|
|
3785
3773
|
}
|
|
3786
3774
|
|
|
3787
3775
|
// src/commands/testers.ts
|
|
3788
|
-
import { readFile as
|
|
3776
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
3789
3777
|
async function listTesters(client, packageName, track) {
|
|
3790
3778
|
const edit = await client.edits.insert(packageName);
|
|
3791
3779
|
try {
|
|
@@ -3832,7 +3820,7 @@ async function removeTesters(client, packageName, track, groupEmails, commitOpti
|
|
|
3832
3820
|
}
|
|
3833
3821
|
}
|
|
3834
3822
|
async function importTestersFromCsv(client, packageName, track, csvPath, commitOptions) {
|
|
3835
|
-
const content = await
|
|
3823
|
+
const content = await readFile5(csvPath, "utf-8");
|
|
3836
3824
|
const emails = content.split(/[,\n\r]+/).map((e) => e.trim()).filter((e) => e.length > 0 && e.includes("@"));
|
|
3837
3825
|
if (emails.length === 0) {
|
|
3838
3826
|
throw new GpcError(
|
|
@@ -3975,12 +3963,12 @@ async function addRecoveryTargeting(client, packageName, actionId, targeting) {
|
|
|
3975
3963
|
}
|
|
3976
3964
|
|
|
3977
3965
|
// src/commands/data-safety.ts
|
|
3978
|
-
import { readFile as
|
|
3966
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
3979
3967
|
async function updateDataSafety(client, packageName, data) {
|
|
3980
3968
|
return client.dataSafety.update(packageName, data);
|
|
3981
3969
|
}
|
|
3982
3970
|
async function importDataSafety(client, packageName, filePath) {
|
|
3983
|
-
const content = await
|
|
3971
|
+
const content = await readFile6(filePath, "utf-8");
|
|
3984
3972
|
let data;
|
|
3985
3973
|
try {
|
|
3986
3974
|
data = JSON.parse(content);
|
|
@@ -4284,16 +4272,16 @@ function createSpinner(message) {
|
|
|
4284
4272
|
}
|
|
4285
4273
|
|
|
4286
4274
|
// src/utils/train-state.ts
|
|
4287
|
-
import { mkdir as mkdir4, readFile as
|
|
4288
|
-
import { dirname, join as
|
|
4275
|
+
import { mkdir as mkdir4, readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
|
|
4276
|
+
import { dirname, join as join5 } from "path";
|
|
4289
4277
|
import { getCacheDir as getCacheDir2 } from "@gpc-cli/config";
|
|
4290
4278
|
function stateFile(packageName) {
|
|
4291
|
-
return
|
|
4279
|
+
return join5(getCacheDir2(), `train-${packageName}.json`);
|
|
4292
4280
|
}
|
|
4293
4281
|
async function readTrainState(packageName) {
|
|
4294
4282
|
const path = stateFile(packageName);
|
|
4295
4283
|
try {
|
|
4296
|
-
const raw = await
|
|
4284
|
+
const raw = await readFile7(path, "utf-8");
|
|
4297
4285
|
return JSON.parse(raw);
|
|
4298
4286
|
} catch {
|
|
4299
4287
|
return null;
|
|
@@ -4473,8 +4461,8 @@ async function publishEnterpriseApp(client, params) {
|
|
|
4473
4461
|
}
|
|
4474
4462
|
|
|
4475
4463
|
// src/audit.ts
|
|
4476
|
-
import { appendFile, chmod, mkdir as mkdir5, readFile as
|
|
4477
|
-
import { join as
|
|
4464
|
+
import { appendFile, chmod, mkdir as mkdir5, readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
|
|
4465
|
+
import { join as join6 } from "path";
|
|
4478
4466
|
var auditDir = null;
|
|
4479
4467
|
function initAudit(configDir) {
|
|
4480
4468
|
auditDir = configDir;
|
|
@@ -4483,7 +4471,7 @@ async function writeAuditLog(entry) {
|
|
|
4483
4471
|
if (!auditDir) return;
|
|
4484
4472
|
try {
|
|
4485
4473
|
await mkdir5(auditDir, { recursive: true, mode: 448 });
|
|
4486
|
-
const logPath =
|
|
4474
|
+
const logPath = join6(auditDir, "audit.log");
|
|
4487
4475
|
const redactedEntry = redactAuditArgs(entry);
|
|
4488
4476
|
const line = JSON.stringify(redactedEntry) + "\n";
|
|
4489
4477
|
await appendFile(logPath, line, { encoding: "utf-8", mode: 384 });
|
|
@@ -4538,10 +4526,10 @@ function createAuditEntry(command, args, app) {
|
|
|
4538
4526
|
}
|
|
4539
4527
|
async function listAuditEvents(options) {
|
|
4540
4528
|
if (!auditDir) return [];
|
|
4541
|
-
const logPath =
|
|
4529
|
+
const logPath = join6(auditDir, "audit.log");
|
|
4542
4530
|
let content;
|
|
4543
4531
|
try {
|
|
4544
|
-
content = await
|
|
4532
|
+
content = await readFile8(logPath, "utf-8");
|
|
4545
4533
|
} catch {
|
|
4546
4534
|
return [];
|
|
4547
4535
|
}
|
|
@@ -4576,10 +4564,10 @@ async function searchAuditEvents(query) {
|
|
|
4576
4564
|
}
|
|
4577
4565
|
async function clearAuditLog(options) {
|
|
4578
4566
|
if (!auditDir) return { deleted: 0, remaining: 0 };
|
|
4579
|
-
const logPath =
|
|
4567
|
+
const logPath = join6(auditDir, "audit.log");
|
|
4580
4568
|
let content;
|
|
4581
4569
|
try {
|
|
4582
|
-
content = await
|
|
4570
|
+
content = await readFile8(logPath, "utf-8");
|
|
4583
4571
|
} catch {
|
|
4584
4572
|
return { deleted: 0, remaining: 0 };
|
|
4585
4573
|
}
|
|
@@ -4667,11 +4655,11 @@ function safePathWithin(userPath, baseDir) {
|
|
|
4667
4655
|
}
|
|
4668
4656
|
|
|
4669
4657
|
// src/commands/init.ts
|
|
4670
|
-
import { mkdir as mkdir6, writeFile as writeFile6, stat as
|
|
4671
|
-
import { join as
|
|
4658
|
+
import { mkdir as mkdir6, writeFile as writeFile6, stat as stat4 } from "fs/promises";
|
|
4659
|
+
import { join as join7 } from "path";
|
|
4672
4660
|
async function exists2(path) {
|
|
4673
4661
|
try {
|
|
4674
|
-
await
|
|
4662
|
+
await stat4(path);
|
|
4675
4663
|
return true;
|
|
4676
4664
|
} catch {
|
|
4677
4665
|
return false;
|
|
@@ -4703,7 +4691,7 @@ async function initProject(options) {
|
|
|
4703
4691
|
null,
|
|
4704
4692
|
2
|
|
4705
4693
|
);
|
|
4706
|
-
await safeWrite(
|
|
4694
|
+
await safeWrite(join7(dir, ".gpcrc.json"), gpcrc + "\n", created, skipped, skipExisting);
|
|
4707
4695
|
const preflightrc = JSON.stringify(
|
|
4708
4696
|
{
|
|
4709
4697
|
failOn: "error",
|
|
@@ -4717,27 +4705,27 @@ async function initProject(options) {
|
|
|
4717
4705
|
2
|
|
4718
4706
|
);
|
|
4719
4707
|
await safeWrite(
|
|
4720
|
-
|
|
4708
|
+
join7(dir, ".preflightrc.json"),
|
|
4721
4709
|
preflightrc + "\n",
|
|
4722
4710
|
created,
|
|
4723
4711
|
skipped,
|
|
4724
4712
|
skipExisting
|
|
4725
4713
|
);
|
|
4726
|
-
const metaDir =
|
|
4727
|
-
await safeWrite(
|
|
4728
|
-
await safeWrite(
|
|
4729
|
-
await safeWrite(
|
|
4730
|
-
await safeWrite(
|
|
4731
|
-
const ssDir =
|
|
4714
|
+
const metaDir = join7(dir, "metadata", "android", "en-US");
|
|
4715
|
+
await safeWrite(join7(metaDir, "title.txt"), "", created, skipped, skipExisting);
|
|
4716
|
+
await safeWrite(join7(metaDir, "short_description.txt"), "", created, skipped, skipExisting);
|
|
4717
|
+
await safeWrite(join7(metaDir, "full_description.txt"), "", created, skipped, skipExisting);
|
|
4718
|
+
await safeWrite(join7(metaDir, "video.txt"), "", created, skipped, skipExisting);
|
|
4719
|
+
const ssDir = join7(metaDir, "images", "phoneScreenshots");
|
|
4732
4720
|
await mkdir6(ssDir, { recursive: true });
|
|
4733
|
-
await safeWrite(
|
|
4721
|
+
await safeWrite(join7(ssDir, ".gitkeep"), "", created, skipped, skipExisting);
|
|
4734
4722
|
if (ci === "github") {
|
|
4735
4723
|
const workflow = githubActionsTemplate(pkg);
|
|
4736
|
-
const workflowDir =
|
|
4737
|
-
await safeWrite(
|
|
4724
|
+
const workflowDir = join7(dir, ".github", "workflows");
|
|
4725
|
+
await safeWrite(join7(workflowDir, "gpc-release.yml"), workflow, created, skipped, skipExisting);
|
|
4738
4726
|
} else if (ci === "gitlab") {
|
|
4739
4727
|
const pipeline = gitlabCiTemplate(pkg);
|
|
4740
|
-
await safeWrite(
|
|
4728
|
+
await safeWrite(join7(dir, ".gitlab-ci-gpc.yml"), pipeline, created, skipped, skipExisting);
|
|
4741
4729
|
}
|
|
4742
4730
|
return { created, skipped };
|
|
4743
4731
|
}
|
|
@@ -4832,13 +4820,13 @@ var DEFAULT_PREFLIGHT_CONFIG = {
|
|
|
4832
4820
|
};
|
|
4833
4821
|
|
|
4834
4822
|
// src/preflight/config.ts
|
|
4835
|
-
import { readFile as
|
|
4823
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
4836
4824
|
var VALID_SEVERITIES = /* @__PURE__ */ new Set(["critical", "error", "warning", "info"]);
|
|
4837
4825
|
async function loadPreflightConfig(configPath) {
|
|
4838
4826
|
const path = configPath || ".preflightrc.json";
|
|
4839
4827
|
let raw;
|
|
4840
4828
|
try {
|
|
4841
|
-
raw = await
|
|
4829
|
+
raw = await readFile9(path, "utf-8");
|
|
4842
4830
|
} catch {
|
|
4843
4831
|
return { ...DEFAULT_PREFLIGHT_CONFIG };
|
|
4844
4832
|
}
|
|
@@ -5755,8 +5743,8 @@ ${fileList}` + (hasPageSizeCompat ? "\nandroid:pageSizeCompat is set, so the app
|
|
|
5755
5743
|
}
|
|
5756
5744
|
|
|
5757
5745
|
// src/preflight/scanners/metadata-scanner.ts
|
|
5758
|
-
import { readdir as
|
|
5759
|
-
import { join as
|
|
5746
|
+
import { readdir as readdir4, stat as stat5, readFile as readFile10 } from "fs/promises";
|
|
5747
|
+
import { join as join8 } from "path";
|
|
5760
5748
|
var SAFE_LANG = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/;
|
|
5761
5749
|
var FILE_MAP2 = {
|
|
5762
5750
|
"title.txt": "title",
|
|
@@ -5782,7 +5770,7 @@ var metadataScanner = {
|
|
|
5782
5770
|
const findings = [];
|
|
5783
5771
|
let entries;
|
|
5784
5772
|
try {
|
|
5785
|
-
entries = await
|
|
5773
|
+
entries = await readdir4(dir);
|
|
5786
5774
|
} catch {
|
|
5787
5775
|
findings.push({
|
|
5788
5776
|
scanner: "metadata",
|
|
@@ -5807,14 +5795,14 @@ var metadataScanner = {
|
|
|
5807
5795
|
return findings;
|
|
5808
5796
|
}
|
|
5809
5797
|
for (const lang of locales) {
|
|
5810
|
-
const langDir =
|
|
5811
|
-
const langStat = await
|
|
5798
|
+
const langDir = join8(dir, lang);
|
|
5799
|
+
const langStat = await stat5(langDir).catch(() => null);
|
|
5812
5800
|
if (!langStat?.isDirectory()) continue;
|
|
5813
5801
|
const fields = {};
|
|
5814
5802
|
for (const [fileName, field] of Object.entries(FILE_MAP2)) {
|
|
5815
|
-
const filePath =
|
|
5803
|
+
const filePath = join8(langDir, fileName);
|
|
5816
5804
|
try {
|
|
5817
|
-
const content = await
|
|
5805
|
+
const content = await readFile10(filePath, "utf-8");
|
|
5818
5806
|
fields[field] = content.trimEnd();
|
|
5819
5807
|
} catch {
|
|
5820
5808
|
}
|
|
@@ -5853,9 +5841,9 @@ var metadataScanner = {
|
|
|
5853
5841
|
let totalScreenshots = 0;
|
|
5854
5842
|
let phoneScreenshots = 0;
|
|
5855
5843
|
for (const ssDir of SCREENSHOT_DIRS) {
|
|
5856
|
-
const ssPath =
|
|
5844
|
+
const ssPath = join8(langDir, "images", ssDir);
|
|
5857
5845
|
try {
|
|
5858
|
-
const ssEntries = await
|
|
5846
|
+
const ssEntries = await readdir4(ssPath);
|
|
5859
5847
|
const imageFiles = ssEntries.filter((f) => /\.(png|jpe?g|webp)$/i.test(f));
|
|
5860
5848
|
totalScreenshots += imageFiles.length;
|
|
5861
5849
|
if (ssDir === "phoneScreenshots") {
|
|
@@ -5885,9 +5873,9 @@ var metadataScanner = {
|
|
|
5885
5873
|
}
|
|
5886
5874
|
}
|
|
5887
5875
|
const defaultLang = locales.includes("en-US") ? "en-US" : locales[0];
|
|
5888
|
-
const privacyPath =
|
|
5876
|
+
const privacyPath = join8(dir, defaultLang, "privacy_policy_url.txt");
|
|
5889
5877
|
try {
|
|
5890
|
-
const url = await
|
|
5878
|
+
const url = await readFile10(privacyPath, "utf-8");
|
|
5891
5879
|
if (!url.trim()) throw new Error("empty");
|
|
5892
5880
|
} catch {
|
|
5893
5881
|
findings.push({
|
|
@@ -5912,11 +5900,11 @@ var metadataScanner = {
|
|
|
5912
5900
|
};
|
|
5913
5901
|
|
|
5914
5902
|
// src/preflight/scanners/secrets-scanner.ts
|
|
5915
|
-
import { readFile as
|
|
5903
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
5916
5904
|
|
|
5917
5905
|
// src/preflight/scan-files.ts
|
|
5918
|
-
import { readdir as
|
|
5919
|
-
import { join as
|
|
5906
|
+
import { readdir as readdir5, stat as stat6 } from "fs/promises";
|
|
5907
|
+
import { join as join9, extname as extname2 } from "path";
|
|
5920
5908
|
var DEFAULT_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
5921
5909
|
".git",
|
|
5922
5910
|
"node_modules",
|
|
@@ -5933,20 +5921,20 @@ async function collectSourceFiles(dir, extensions, skipDirs = DEFAULT_SKIP_DIRS,
|
|
|
5933
5921
|
const files = [];
|
|
5934
5922
|
let entries;
|
|
5935
5923
|
try {
|
|
5936
|
-
entries = await
|
|
5924
|
+
entries = await readdir5(dir);
|
|
5937
5925
|
} catch {
|
|
5938
5926
|
return files;
|
|
5939
5927
|
}
|
|
5940
5928
|
for (const entry of entries) {
|
|
5941
5929
|
if (skipDirs.has(entry)) continue;
|
|
5942
|
-
const fullPath =
|
|
5943
|
-
const s = await
|
|
5930
|
+
const fullPath = join9(dir, entry);
|
|
5931
|
+
const s = await stat6(fullPath).catch(() => null);
|
|
5944
5932
|
if (!s) continue;
|
|
5945
5933
|
if (s.isDirectory()) {
|
|
5946
5934
|
const sub = await collectSourceFiles(fullPath, extensions, skipDirs, maxDepth - 1);
|
|
5947
5935
|
files.push(...sub);
|
|
5948
5936
|
} else if (s.isFile()) {
|
|
5949
|
-
const ext =
|
|
5937
|
+
const ext = extname2(entry).toLowerCase();
|
|
5950
5938
|
if (extensions.has(ext) || entry.endsWith(".gradle.kts")) {
|
|
5951
5939
|
files.push(fullPath);
|
|
5952
5940
|
}
|
|
@@ -6032,7 +6020,7 @@ var secretsScanner = {
|
|
|
6032
6020
|
for (const filePath of files) {
|
|
6033
6021
|
let content;
|
|
6034
6022
|
try {
|
|
6035
|
-
content = await
|
|
6023
|
+
content = await readFile11(filePath, "utf-8");
|
|
6036
6024
|
} catch {
|
|
6037
6025
|
continue;
|
|
6038
6026
|
}
|
|
@@ -6060,7 +6048,7 @@ var secretsScanner = {
|
|
|
6060
6048
|
};
|
|
6061
6049
|
|
|
6062
6050
|
// src/preflight/scanners/billing-scanner.ts
|
|
6063
|
-
import { readFile as
|
|
6051
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
6064
6052
|
var BILLING_PATTERNS = [
|
|
6065
6053
|
{
|
|
6066
6054
|
ruleId: "billing-stripe-sdk",
|
|
@@ -6121,7 +6109,7 @@ var billingScanner = {
|
|
|
6121
6109
|
for (const filePath of files) {
|
|
6122
6110
|
let content;
|
|
6123
6111
|
try {
|
|
6124
|
-
content = await
|
|
6112
|
+
content = await readFile12(filePath, "utf-8");
|
|
6125
6113
|
} catch {
|
|
6126
6114
|
continue;
|
|
6127
6115
|
}
|
|
@@ -6147,7 +6135,7 @@ var billingScanner = {
|
|
|
6147
6135
|
};
|
|
6148
6136
|
|
|
6149
6137
|
// src/preflight/scanners/privacy-scanner.ts
|
|
6150
|
-
import { readFile as
|
|
6138
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
6151
6139
|
var TRACKING_SDKS = [
|
|
6152
6140
|
{
|
|
6153
6141
|
name: "Facebook SDK",
|
|
@@ -6187,7 +6175,7 @@ var privacyScanner = {
|
|
|
6187
6175
|
for (const filePath of files) {
|
|
6188
6176
|
let content;
|
|
6189
6177
|
try {
|
|
6190
|
-
content = await
|
|
6178
|
+
content = await readFile13(filePath, "utf-8");
|
|
6191
6179
|
} catch {
|
|
6192
6180
|
continue;
|
|
6193
6181
|
}
|
|
@@ -6560,13 +6548,13 @@ function sortResults(items, sortSpec) {
|
|
|
6560
6548
|
|
|
6561
6549
|
// src/commands/plugin-scaffold.ts
|
|
6562
6550
|
import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
|
|
6563
|
-
import { join as
|
|
6551
|
+
import { join as join10 } from "path";
|
|
6564
6552
|
async function scaffoldPlugin(options) {
|
|
6565
6553
|
const { name, dir, description = `GPC plugin: ${name}` } = options;
|
|
6566
6554
|
const pluginName = name.startsWith("gpc-plugin-") ? name : `gpc-plugin-${name}`;
|
|
6567
6555
|
const shortName = pluginName.replace(/^gpc-plugin-/, "");
|
|
6568
|
-
const srcDir =
|
|
6569
|
-
const testDir =
|
|
6556
|
+
const srcDir = join10(dir, "src");
|
|
6557
|
+
const testDir = join10(dir, "tests");
|
|
6570
6558
|
await mkdir7(srcDir, { recursive: true });
|
|
6571
6559
|
await mkdir7(testDir, { recursive: true });
|
|
6572
6560
|
const files = [];
|
|
@@ -6602,7 +6590,7 @@ async function scaffoldPlugin(options) {
|
|
|
6602
6590
|
vitest: "^3.0.0"
|
|
6603
6591
|
}
|
|
6604
6592
|
};
|
|
6605
|
-
await writeFile7(
|
|
6593
|
+
await writeFile7(join10(dir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
|
|
6606
6594
|
files.push("package.json");
|
|
6607
6595
|
const tsconfig = {
|
|
6608
6596
|
compilerOptions: {
|
|
@@ -6618,7 +6606,7 @@ async function scaffoldPlugin(options) {
|
|
|
6618
6606
|
},
|
|
6619
6607
|
include: ["src"]
|
|
6620
6608
|
};
|
|
6621
|
-
await writeFile7(
|
|
6609
|
+
await writeFile7(join10(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
|
|
6622
6610
|
files.push("tsconfig.json");
|
|
6623
6611
|
const srcContent = `import { definePlugin } from "@gpc-cli/plugin-sdk";
|
|
6624
6612
|
import type { CommandEvent, CommandResult } from "@gpc-cli/plugin-sdk";
|
|
@@ -6651,7 +6639,7 @@ export const plugin = definePlugin({
|
|
|
6651
6639
|
},
|
|
6652
6640
|
});
|
|
6653
6641
|
`;
|
|
6654
|
-
await writeFile7(
|
|
6642
|
+
await writeFile7(join10(srcDir, "index.ts"), srcContent);
|
|
6655
6643
|
files.push("src/index.ts");
|
|
6656
6644
|
const testContent = `import { describe, it, expect, vi } from "vitest";
|
|
6657
6645
|
import { plugin } from "../src/index";
|
|
@@ -6676,7 +6664,7 @@ describe("${pluginName}", () => {
|
|
|
6676
6664
|
});
|
|
6677
6665
|
});
|
|
6678
6666
|
`;
|
|
6679
|
-
await writeFile7(
|
|
6667
|
+
await writeFile7(join10(testDir, "plugin.test.ts"), testContent);
|
|
6680
6668
|
files.push("tests/plugin.test.ts");
|
|
6681
6669
|
return { dir, files };
|
|
6682
6670
|
}
|
|
@@ -6787,7 +6775,7 @@ async function sendWebhook(config, payload, target) {
|
|
|
6787
6775
|
}
|
|
6788
6776
|
|
|
6789
6777
|
// src/commands/internal-sharing.ts
|
|
6790
|
-
import { extname as
|
|
6778
|
+
import { extname as extname3 } from "path";
|
|
6791
6779
|
async function uploadInternalSharing(client, packageName, filePath, fileType) {
|
|
6792
6780
|
const resolvedType = fileType ?? detectFileType(filePath);
|
|
6793
6781
|
const validation = await validateUploadFile(filePath);
|
|
@@ -6814,7 +6802,7 @@ ${validation.errors.join("\n")}`,
|
|
|
6814
6802
|
};
|
|
6815
6803
|
}
|
|
6816
6804
|
function detectFileType(filePath) {
|
|
6817
|
-
const ext =
|
|
6805
|
+
const ext = extname3(filePath).toLowerCase();
|
|
6818
6806
|
if (ext === ".aab") return "bundle";
|
|
6819
6807
|
if (ext === ".apk") return "apk";
|
|
6820
6808
|
throw new GpcError(
|
|
@@ -6862,7 +6850,7 @@ async function downloadGeneratedApk(client, packageName, versionCode, apkId, out
|
|
|
6862
6850
|
}
|
|
6863
6851
|
|
|
6864
6852
|
// src/commands/bundle-analysis.ts
|
|
6865
|
-
import { readFile as
|
|
6853
|
+
import { readFile as readFile14, stat as stat7 } from "fs/promises";
|
|
6866
6854
|
var EOCD_SIGNATURE = 101010256;
|
|
6867
6855
|
var CD_SIGNATURE = 33639248;
|
|
6868
6856
|
var MODULE_SUBDIRS = /* @__PURE__ */ new Set(["dex", "manifest", "res", "assets", "lib", "resources.pb", "root"]);
|
|
@@ -6938,11 +6926,11 @@ function detectFileType2(filePath) {
|
|
|
6938
6926
|
return "apk";
|
|
6939
6927
|
}
|
|
6940
6928
|
async function analyzeBundle(filePath) {
|
|
6941
|
-
const fileInfo = await
|
|
6929
|
+
const fileInfo = await stat7(filePath).catch(() => null);
|
|
6942
6930
|
if (!fileInfo || !fileInfo.isFile()) {
|
|
6943
6931
|
throw new Error(`File not found: ${filePath}`);
|
|
6944
6932
|
}
|
|
6945
|
-
const buf = await
|
|
6933
|
+
const buf = await readFile14(filePath);
|
|
6946
6934
|
const cdEntries = parseCentralDirectory(buf);
|
|
6947
6935
|
const fileType = detectFileType2(filePath);
|
|
6948
6936
|
const isAab = fileType === "aab";
|
|
@@ -7028,7 +7016,7 @@ function topFiles(analysis, n = 20) {
|
|
|
7028
7016
|
async function checkBundleSize(analysis, configPath = ".bundlesize.json") {
|
|
7029
7017
|
let config;
|
|
7030
7018
|
try {
|
|
7031
|
-
const raw = await
|
|
7019
|
+
const raw = await readFile14(configPath, "utf-8");
|
|
7032
7020
|
config = JSON.parse(raw);
|
|
7033
7021
|
} catch {
|
|
7034
7022
|
return { passed: true, violations: [] };
|
|
@@ -8351,10 +8339,10 @@ function compareFingerprints(a, b) {
|
|
|
8351
8339
|
|
|
8352
8340
|
// src/utils/hash.ts
|
|
8353
8341
|
import { createHash } from "crypto";
|
|
8354
|
-
import { stat as
|
|
8342
|
+
import { stat as stat8 } from "fs/promises";
|
|
8355
8343
|
async function sha256File(filePath) {
|
|
8356
8344
|
const hash = createHash("sha256");
|
|
8357
|
-
const { size } = await
|
|
8345
|
+
const { size } = await stat8(filePath);
|
|
8358
8346
|
if (size === 0) return hash.digest("hex");
|
|
8359
8347
|
const { createReadStream } = await import("fs");
|
|
8360
8348
|
const stream = createReadStream(filePath);
|
|
@@ -8367,8 +8355,8 @@ async function sha256File(filePath) {
|
|
|
8367
8355
|
}
|
|
8368
8356
|
|
|
8369
8357
|
// src/commands/image-sync.ts
|
|
8370
|
-
import { readdir as
|
|
8371
|
-
import { join as
|
|
8358
|
+
import { readdir as readdir6 } from "fs/promises";
|
|
8359
|
+
import { join as join11, extname as extname4 } from "path";
|
|
8372
8360
|
import { PlayApiError } from "@gpc-cli/api";
|
|
8373
8361
|
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg"]);
|
|
8374
8362
|
var ALL_IMAGE_TYPES2 = [
|
|
@@ -8383,15 +8371,15 @@ var ALL_IMAGE_TYPES2 = [
|
|
|
8383
8371
|
];
|
|
8384
8372
|
async function scanLocalImages(dir) {
|
|
8385
8373
|
try {
|
|
8386
|
-
const entries = await
|
|
8387
|
-
return entries.filter((e) => e.isFile() && IMAGE_EXTENSIONS.has(
|
|
8374
|
+
const entries = await readdir6(dir, { withFileTypes: true });
|
|
8375
|
+
return entries.filter((e) => e.isFile() && IMAGE_EXTENSIONS.has(extname4(e.name).toLowerCase())).map((e) => e.name).sort();
|
|
8388
8376
|
} catch {
|
|
8389
8377
|
return [];
|
|
8390
8378
|
}
|
|
8391
8379
|
}
|
|
8392
8380
|
async function scanLanguages(dir) {
|
|
8393
8381
|
try {
|
|
8394
|
-
const entries = await
|
|
8382
|
+
const entries = await readdir6(dir, { withFileTypes: true });
|
|
8395
8383
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
8396
8384
|
} catch {
|
|
8397
8385
|
return [];
|
|
@@ -8416,7 +8404,7 @@ async function syncImages(client, packageName, dir, options) {
|
|
|
8416
8404
|
try {
|
|
8417
8405
|
for (const language of languages) {
|
|
8418
8406
|
for (const imageType of imageTypes) {
|
|
8419
|
-
const localDir =
|
|
8407
|
+
const localDir = join11(dir, language, imageType);
|
|
8420
8408
|
const localFiles = await scanLocalImages(localDir);
|
|
8421
8409
|
let remoteImages;
|
|
8422
8410
|
try {
|
|
@@ -8431,7 +8419,7 @@ async function syncImages(client, packageName, dir, options) {
|
|
|
8431
8419
|
const remoteSha256Set = new Set(remoteImages.map((img) => img.sha256.toLowerCase()));
|
|
8432
8420
|
const localHashes = /* @__PURE__ */ new Map();
|
|
8433
8421
|
for (const file of localFiles) {
|
|
8434
|
-
const hash = await sha256File(
|
|
8422
|
+
const hash = await sha256File(join11(localDir, file));
|
|
8435
8423
|
localHashes.set(file, hash);
|
|
8436
8424
|
}
|
|
8437
8425
|
const localSha256Set = new Set(localHashes.values());
|
|
@@ -8459,7 +8447,7 @@ async function syncImages(client, packageName, dir, options) {
|
|
|
8459
8447
|
details.push({ language, imageType, file, action: "skip", reason: "sha256 match" });
|
|
8460
8448
|
} else {
|
|
8461
8449
|
if (!options?.dryRun) {
|
|
8462
|
-
const filePath =
|
|
8450
|
+
const filePath = join11(localDir, file);
|
|
8463
8451
|
const check = await validateImage(filePath, imageType);
|
|
8464
8452
|
if (!check.valid) {
|
|
8465
8453
|
throw new GpcError(
|
|
@@ -8904,6 +8892,7 @@ export {
|
|
|
8904
8892
|
isValidBcp47,
|
|
8905
8893
|
isValidReportType,
|
|
8906
8894
|
isValidStatsDimension,
|
|
8895
|
+
isVersionedNotesDir,
|
|
8907
8896
|
lintListing,
|
|
8908
8897
|
lintListings,
|
|
8909
8898
|
lintLocalListings,
|
|
@@ -8947,6 +8936,7 @@ export {
|
|
|
8947
8936
|
pullListings,
|
|
8948
8937
|
pushListings,
|
|
8949
8938
|
readListingsFromDir,
|
|
8939
|
+
readReleaseNotesForVersion,
|
|
8950
8940
|
readReleaseNotesFromDir,
|
|
8951
8941
|
redactAuditArgs,
|
|
8952
8942
|
redactSensitive,
|