@gpc-cli/core 0.9.78 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +27 -3
- package/dist/index.js +309 -127
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -443,7 +443,15 @@ function formatJunit(data, commandName = "command") {
|
|
|
443
443
|
}
|
|
444
444
|
|
|
445
445
|
// src/plugins.ts
|
|
446
|
-
|
|
446
|
+
import { readFile } from "fs/promises";
|
|
447
|
+
import { dirname, join, parse } from "path";
|
|
448
|
+
import { fileURLToPath } from "url";
|
|
449
|
+
import { resolvePluginApprovalId } from "@gpc-cli/config";
|
|
450
|
+
var FIRST_PARTY_PLUGINS = /* @__PURE__ */ new Set(["@gpc-cli/plugin-ci"]);
|
|
451
|
+
var discoveredPluginManifests = /* @__PURE__ */ new WeakMap();
|
|
452
|
+
function isFirstPartyPluginSpecifier(specifier) {
|
|
453
|
+
return FIRST_PARTY_PLUGINS.has(specifier);
|
|
454
|
+
}
|
|
447
455
|
var PluginManager = class {
|
|
448
456
|
plugins = [];
|
|
449
457
|
beforeHandlers = [];
|
|
@@ -454,19 +462,34 @@ var PluginManager = class {
|
|
|
454
462
|
registeredCommands = [];
|
|
455
463
|
/** Load and register a plugin */
|
|
456
464
|
async load(plugin, manifest) {
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
465
|
+
const effectiveManifest = manifest ?? discoveredPluginManifests.get(plugin);
|
|
466
|
+
const isTrusted = effectiveManifest ? effectiveManifest.trusted === true : FIRST_PARTY_PLUGINS.has(plugin.name);
|
|
467
|
+
if (!isTrusted && effectiveManifest) {
|
|
468
|
+
if (!effectiveManifest.permissions) {
|
|
469
|
+
throw new GpcError(
|
|
470
|
+
`Third-party plugin "${effectiveManifest.name}" must declare permissions in package.json under gpc.permissions`,
|
|
471
|
+
"PLUGIN_PERMISSIONS_REQUIRED",
|
|
472
|
+
10,
|
|
473
|
+
"Declare only the lifecycle hooks the plugin needs, then reinstall or relink it."
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
validatePermissions(effectiveManifest.permissions);
|
|
477
|
+
}
|
|
478
|
+
const stagedBeforeHandlers = [];
|
|
479
|
+
const stagedAfterHandlers = [];
|
|
480
|
+
const stagedErrorHandlers = [];
|
|
481
|
+
const stagedBeforeRequestHandlers = [];
|
|
482
|
+
const stagedAfterResponseHandlers = [];
|
|
483
|
+
const stagedCommands = [];
|
|
461
484
|
const allHooks = createHooks(
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
485
|
+
stagedBeforeHandlers,
|
|
486
|
+
stagedAfterHandlers,
|
|
487
|
+
stagedErrorHandlers,
|
|
488
|
+
stagedBeforeRequestHandlers,
|
|
489
|
+
stagedAfterResponseHandlers,
|
|
490
|
+
stagedCommands
|
|
468
491
|
);
|
|
469
|
-
const hooks = isTrusted || !
|
|
492
|
+
const hooks = isTrusted || !effectiveManifest ? allHooks : createRestrictedHooks(allHooks, new Set(effectiveManifest.permissions));
|
|
470
493
|
try {
|
|
471
494
|
await plugin.register(hooks);
|
|
472
495
|
} catch (err) {
|
|
@@ -474,9 +497,15 @@ var PluginManager = class {
|
|
|
474
497
|
console.warn(`[gpc] Plugin "${plugin.name}" failed to register: ${msg}`);
|
|
475
498
|
return;
|
|
476
499
|
}
|
|
500
|
+
this.beforeHandlers.push(...stagedBeforeHandlers);
|
|
501
|
+
this.afterHandlers.push(...stagedAfterHandlers);
|
|
502
|
+
this.errorHandlers.push(...stagedErrorHandlers);
|
|
503
|
+
this.beforeRequestHandlers.push(...stagedBeforeRequestHandlers);
|
|
504
|
+
this.afterResponseHandlers.push(...stagedAfterResponseHandlers);
|
|
505
|
+
this.registeredCommands.push(...stagedCommands);
|
|
477
506
|
this.plugins.push({
|
|
478
|
-
name: plugin.name,
|
|
479
|
-
version: plugin.version,
|
|
507
|
+
name: effectiveManifest?.name ?? plugin.name,
|
|
508
|
+
version: effectiveManifest?.version ?? plugin.version,
|
|
480
509
|
trusted: isTrusted
|
|
481
510
|
});
|
|
482
511
|
}
|
|
@@ -489,7 +518,10 @@ var PluginManager = class {
|
|
|
489
518
|
/** Run all afterCommand handlers */
|
|
490
519
|
async runAfterCommand(event, result) {
|
|
491
520
|
for (const handler of this.afterHandlers) {
|
|
492
|
-
|
|
521
|
+
try {
|
|
522
|
+
await handler(event, result);
|
|
523
|
+
} catch {
|
|
524
|
+
}
|
|
493
525
|
}
|
|
494
526
|
}
|
|
495
527
|
/** Run all onError handlers */
|
|
@@ -577,6 +609,7 @@ var HOOK_PERMISSIONS = {
|
|
|
577
609
|
afterResponse: "hooks:afterResponse",
|
|
578
610
|
registerCommands: "commands:register"
|
|
579
611
|
};
|
|
612
|
+
var LEGACY_PLUGIN_PERMISSIONS = Object.values(HOOK_PERMISSIONS);
|
|
580
613
|
function createRestrictedHooks(inner, permissions) {
|
|
581
614
|
function gate(key) {
|
|
582
615
|
const requiredPerm = HOOK_PERMISSIONS[key];
|
|
@@ -621,31 +654,148 @@ function validatePermissions(permissions) {
|
|
|
621
654
|
}
|
|
622
655
|
}
|
|
623
656
|
}
|
|
624
|
-
function isPluginTrusted(specifier, approved) {
|
|
625
|
-
if (specifier
|
|
626
|
-
return approved?.has(
|
|
657
|
+
function isPluginTrusted(specifier, approvalId, approved) {
|
|
658
|
+
if (isFirstPartyPluginSpecifier(specifier)) return true;
|
|
659
|
+
return approved?.has(approvalId) ?? false;
|
|
627
660
|
}
|
|
628
|
-
async function
|
|
661
|
+
async function discoverPluginEntries(options) {
|
|
629
662
|
const plugins = [];
|
|
630
663
|
const seen = /* @__PURE__ */ new Set();
|
|
631
|
-
const
|
|
664
|
+
const cwd = options?.cwd;
|
|
665
|
+
const approved = options?.approvedPlugins ? new Set(options.approvedPlugins.map((name) => resolvePluginApprovalId(name, cwd))) : void 0;
|
|
666
|
+
const legacyApproved = new Set(
|
|
667
|
+
(options?.legacyApprovedPlugins ?? []).map((name) => resolvePluginApprovalId(name, cwd))
|
|
668
|
+
);
|
|
632
669
|
if (options?.configPlugins) {
|
|
633
670
|
for (const name of options.configPlugins) {
|
|
634
|
-
|
|
635
|
-
if (
|
|
671
|
+
const resolvedSpecifier = resolvePluginApprovalId(name, cwd);
|
|
672
|
+
if (seen.has(resolvedSpecifier)) continue;
|
|
673
|
+
if (!isPluginTrusted(name, resolvedSpecifier, approved)) continue;
|
|
636
674
|
try {
|
|
637
|
-
const
|
|
675
|
+
const manifestInfo = await readPluginManifest(resolvedSpecifier, options?.resolveSpecifier);
|
|
676
|
+
const claimsFirstParty = isFirstPartyPluginSpecifier(name);
|
|
677
|
+
const trusted = claimsFirstParty && manifestInfo.packageFound && manifestInfo.manifest.name === name;
|
|
678
|
+
if (claimsFirstParty && !trusted) {
|
|
679
|
+
throw new GpcError(
|
|
680
|
+
`Configured first-party plugin "${name}" resolved to package "${manifestInfo.manifest.name}"`,
|
|
681
|
+
"PLUGIN_IDENTITY_MISMATCH",
|
|
682
|
+
10,
|
|
683
|
+
"Remove npm aliases or replacements for first-party GPC plugin packages."
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
if (!trusted && manifestInfo.legacyPermissions && !legacyApproved.has(resolvedSpecifier)) {
|
|
687
|
+
throw new GpcError(
|
|
688
|
+
`Third-party plugin "${manifestInfo.manifest.name}" must declare permissions in package.json under gpc.permissions`,
|
|
689
|
+
"PLUGIN_PERMISSIONS_REQUIRED",
|
|
690
|
+
10,
|
|
691
|
+
"Ask the plugin author to publish an explicit permission list before approving it."
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
if (!trusted) validatePermissions(manifestInfo.manifest.permissions ?? []);
|
|
695
|
+
const mod = await import(resolvedSpecifier);
|
|
638
696
|
const plugin = resolvePlugin(mod);
|
|
639
697
|
if (plugin) {
|
|
640
|
-
|
|
641
|
-
|
|
698
|
+
const manifest = trusted ? {
|
|
699
|
+
name: manifestInfo.manifest.name,
|
|
700
|
+
version: manifestInfo.manifest.version,
|
|
701
|
+
trusted: true
|
|
702
|
+
} : manifestInfo.manifest;
|
|
703
|
+
const entry = {
|
|
704
|
+
plugin,
|
|
705
|
+
manifest,
|
|
706
|
+
legacyPermissions: manifestInfo?.legacyPermissions ?? false
|
|
707
|
+
};
|
|
708
|
+
discoveredPluginManifests.set(plugin, entry.manifest);
|
|
709
|
+
plugins.push(entry);
|
|
710
|
+
seen.add(resolvedSpecifier);
|
|
711
|
+
}
|
|
712
|
+
} catch (error) {
|
|
713
|
+
if (error instanceof GpcError && error.code.startsWith("PLUGIN_")) {
|
|
714
|
+
console.warn(`[gpc] Skipping plugin "${name}": ${error.message}`);
|
|
642
715
|
}
|
|
643
|
-
} catch {
|
|
644
716
|
}
|
|
645
717
|
}
|
|
646
718
|
}
|
|
647
719
|
return plugins;
|
|
648
720
|
}
|
|
721
|
+
async function discoverPlugins(options) {
|
|
722
|
+
const compatibilityOptions = options?.legacyApprovedPlugins === void 0 ? { ...options, legacyApprovedPlugins: options?.approvedPlugins } : options;
|
|
723
|
+
const entries = await discoverPluginEntries(compatibilityOptions);
|
|
724
|
+
return entries.map(({ plugin }) => plugin);
|
|
725
|
+
}
|
|
726
|
+
async function validatePluginForApproval(specifier, cwd) {
|
|
727
|
+
const resolvedSpecifier = resolvePluginApprovalId(specifier, cwd);
|
|
728
|
+
const manifestInfo = await readPluginManifest(resolvedSpecifier);
|
|
729
|
+
if (manifestInfo.legacyPermissions) {
|
|
730
|
+
throw new GpcError(
|
|
731
|
+
`Third-party plugin "${manifestInfo.manifest.name}" must declare permissions in package.json under gpc.permissions`,
|
|
732
|
+
"PLUGIN_PERMISSIONS_REQUIRED",
|
|
733
|
+
10,
|
|
734
|
+
"Ask the plugin author to publish an explicit permission list before approving it."
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
validatePermissions(manifestInfo.manifest.permissions ?? []);
|
|
738
|
+
return { specifier: resolvedSpecifier, manifest: manifestInfo.manifest };
|
|
739
|
+
}
|
|
740
|
+
async function readPluginManifest(specifier, resolveSpecifier) {
|
|
741
|
+
const { packageJson, adjacent } = await findPackageJson(specifier, resolveSpecifier);
|
|
742
|
+
const gpc = packageJson?.["gpc"] && typeof packageJson["gpc"] === "object" ? packageJson["gpc"] : void 0;
|
|
743
|
+
const permissionDeclaration = gpc?.["permissions"];
|
|
744
|
+
const declaresPluginMetadata = permissionDeclaration !== void 0;
|
|
745
|
+
const identifyByPath = specifier.startsWith("file:") && !declaresPluginMetadata && !adjacent;
|
|
746
|
+
const packageName = !identifyByPath && typeof packageJson?.["name"] === "string" ? packageJson["name"] : specifier;
|
|
747
|
+
const packageVersion = !identifyByPath && typeof packageJson?.["version"] === "string" ? packageJson["version"] : "unknown";
|
|
748
|
+
if (permissionDeclaration !== void 0 && !Array.isArray(permissionDeclaration)) {
|
|
749
|
+
throw new GpcError(
|
|
750
|
+
`Plugin "${packageName}" must declare gpc.permissions as an array`,
|
|
751
|
+
"PLUGIN_INVALID_PERMISSION",
|
|
752
|
+
10,
|
|
753
|
+
"Declare permissions as a JSON array of supported permission names."
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
const permissions = permissionDeclaration;
|
|
757
|
+
return {
|
|
758
|
+
manifest: {
|
|
759
|
+
name: packageName,
|
|
760
|
+
version: packageVersion,
|
|
761
|
+
permissions: permissions ?? [...LEGACY_PLUGIN_PERMISSIONS],
|
|
762
|
+
trusted: false
|
|
763
|
+
},
|
|
764
|
+
legacyPermissions: permissions === void 0,
|
|
765
|
+
packageFound: packageJson !== void 0
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
async function findPackageJson(specifier, resolveSpecifier = import.meta.resolve) {
|
|
769
|
+
let resolved;
|
|
770
|
+
try {
|
|
771
|
+
resolved = resolveSpecifier(specifier);
|
|
772
|
+
} catch {
|
|
773
|
+
return { adjacent: false };
|
|
774
|
+
}
|
|
775
|
+
if (!resolved.startsWith("file:")) return { adjacent: false };
|
|
776
|
+
const moduleDirectory = dirname(fileURLToPath(resolved));
|
|
777
|
+
let directory = moduleDirectory;
|
|
778
|
+
const root = parse(directory).root;
|
|
779
|
+
while (directory !== root) {
|
|
780
|
+
let contents;
|
|
781
|
+
try {
|
|
782
|
+
contents = await readFile(join(directory, "package.json"), "utf8");
|
|
783
|
+
} catch (error) {
|
|
784
|
+
if (error.code !== "ENOENT") return { adjacent: false };
|
|
785
|
+
directory = dirname(directory);
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
try {
|
|
789
|
+
return {
|
|
790
|
+
packageJson: JSON.parse(contents),
|
|
791
|
+
adjacent: directory === moduleDirectory
|
|
792
|
+
};
|
|
793
|
+
} catch {
|
|
794
|
+
return { adjacent: false };
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return { adjacent: false };
|
|
798
|
+
}
|
|
649
799
|
function resolvePlugin(mod) {
|
|
650
800
|
if (!mod || typeof mod !== "object") return void 0;
|
|
651
801
|
const m = mod;
|
|
@@ -820,8 +970,8 @@ async function validateImage(filePath, imageType) {
|
|
|
820
970
|
}
|
|
821
971
|
|
|
822
972
|
// src/utils/fastlane.ts
|
|
823
|
-
import { readFile, writeFile, mkdir, readdir, stat as stat2 } from "fs/promises";
|
|
824
|
-
import { join } from "path";
|
|
973
|
+
import { readFile as readFile2, writeFile, mkdir, readdir, stat as stat2 } from "fs/promises";
|
|
974
|
+
import { join as join2 } from "path";
|
|
825
975
|
var FILE_MAP = {
|
|
826
976
|
"title.txt": "title",
|
|
827
977
|
"short_description.txt": "shortDescription",
|
|
@@ -846,7 +996,7 @@ async function readListingsFromDir(dir) {
|
|
|
846
996
|
const SAFE_LANG2 = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/;
|
|
847
997
|
for (const lang of entries) {
|
|
848
998
|
if (!SAFE_LANG2.test(lang)) continue;
|
|
849
|
-
const langDir =
|
|
999
|
+
const langDir = join2(dir, lang);
|
|
850
1000
|
const langStat = await stat2(langDir);
|
|
851
1001
|
if (!langStat.isDirectory()) continue;
|
|
852
1002
|
const listing = {
|
|
@@ -856,9 +1006,9 @@ async function readListingsFromDir(dir) {
|
|
|
856
1006
|
fullDescription: ""
|
|
857
1007
|
};
|
|
858
1008
|
for (const [fileName, field] of Object.entries(FILE_MAP)) {
|
|
859
|
-
const filePath =
|
|
1009
|
+
const filePath = join2(langDir, fileName);
|
|
860
1010
|
if (await exists(filePath)) {
|
|
861
|
-
const content = await
|
|
1011
|
+
const content = await readFile2(filePath, "utf-8");
|
|
862
1012
|
listing[field] = content.trimEnd();
|
|
863
1013
|
}
|
|
864
1014
|
}
|
|
@@ -868,12 +1018,12 @@ async function readListingsFromDir(dir) {
|
|
|
868
1018
|
}
|
|
869
1019
|
async function writeListingsToDir(dir, listings) {
|
|
870
1020
|
for (const listing of listings) {
|
|
871
|
-
const langDir =
|
|
1021
|
+
const langDir = join2(dir, listing.language);
|
|
872
1022
|
await mkdir(langDir, { recursive: true });
|
|
873
1023
|
for (const [field, fileName] of Object.entries(FIELD_TO_FILE)) {
|
|
874
1024
|
const value = listing[field];
|
|
875
1025
|
if (value !== void 0 && value !== "") {
|
|
876
|
-
await writeFile(
|
|
1026
|
+
await writeFile(join2(langDir, fileName), value + "\n", "utf-8");
|
|
877
1027
|
}
|
|
878
1028
|
}
|
|
879
1029
|
}
|
|
@@ -1248,7 +1398,7 @@ var ALL_IMAGE_TYPES = [
|
|
|
1248
1398
|
];
|
|
1249
1399
|
async function exportImages(client, packageName, dir, options) {
|
|
1250
1400
|
const { mkdir: mkdir9, writeFile: writeFile10 } = await import("fs/promises");
|
|
1251
|
-
const { join:
|
|
1401
|
+
const { join: join14 } = await import("path");
|
|
1252
1402
|
const edit = await client.edits.insert(packageName);
|
|
1253
1403
|
try {
|
|
1254
1404
|
let languages;
|
|
@@ -1279,7 +1429,7 @@ async function exportImages(client, packageName, dir, options) {
|
|
|
1279
1429
|
const batch = tasks.slice(i, i + concurrency);
|
|
1280
1430
|
const results = await Promise.all(
|
|
1281
1431
|
batch.map(async (task) => {
|
|
1282
|
-
const dirPath =
|
|
1432
|
+
const dirPath = join14(dir, task.language, task.imageType);
|
|
1283
1433
|
await mkdir9(dirPath, { recursive: true });
|
|
1284
1434
|
const response = await fetch(task.url);
|
|
1285
1435
|
if (!response.ok) {
|
|
@@ -1291,7 +1441,7 @@ async function exportImages(client, packageName, dir, options) {
|
|
|
1291
1441
|
);
|
|
1292
1442
|
}
|
|
1293
1443
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
1294
|
-
const filePath =
|
|
1444
|
+
const filePath = join14(dirPath, `${task.index}.png`);
|
|
1295
1445
|
await writeFile10(filePath, buffer);
|
|
1296
1446
|
return buffer.length;
|
|
1297
1447
|
})
|
|
@@ -1327,8 +1477,8 @@ async function updateAppDetails(client, packageName, details, commitOptions) {
|
|
|
1327
1477
|
}
|
|
1328
1478
|
|
|
1329
1479
|
// src/commands/migrate.ts
|
|
1330
|
-
import { readdir as readdir2, readFile as
|
|
1331
|
-
import { join as
|
|
1480
|
+
import { readdir as readdir2, readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2, access } from "fs/promises";
|
|
1481
|
+
import { join as join3 } from "path";
|
|
1332
1482
|
var COMPLEX_RUBY_RE = /\b(begin|rescue|ensure|if |unless |case |while |until |for )\b/;
|
|
1333
1483
|
async function fileExists(path) {
|
|
1334
1484
|
try {
|
|
@@ -1348,14 +1498,14 @@ async function detectFastlane(cwd) {
|
|
|
1348
1498
|
metadataLanguages: [],
|
|
1349
1499
|
parseWarnings: []
|
|
1350
1500
|
};
|
|
1351
|
-
const fastlaneDir =
|
|
1501
|
+
const fastlaneDir = join3(cwd, "fastlane");
|
|
1352
1502
|
const hasFastlaneDir = await fileExists(fastlaneDir);
|
|
1353
|
-
const fastfilePath = hasFastlaneDir ?
|
|
1354
|
-
const appfilePath = hasFastlaneDir ?
|
|
1503
|
+
const fastfilePath = hasFastlaneDir ? join3(fastlaneDir, "Fastfile") : join3(cwd, "Fastfile");
|
|
1504
|
+
const appfilePath = hasFastlaneDir ? join3(fastlaneDir, "Appfile") : join3(cwd, "Appfile");
|
|
1355
1505
|
result.hasFastfile = await fileExists(fastfilePath);
|
|
1356
1506
|
result.hasAppfile = await fileExists(appfilePath);
|
|
1357
|
-
result.hasGemfile = await fileExists(
|
|
1358
|
-
const metadataDir = hasFastlaneDir ?
|
|
1507
|
+
result.hasGemfile = await fileExists(join3(cwd, "Gemfile"));
|
|
1508
|
+
const metadataDir = hasFastlaneDir ? join3(fastlaneDir, "metadata", "android") : join3(cwd, "metadata", "android");
|
|
1359
1509
|
result.hasMetadata = await fileExists(metadataDir);
|
|
1360
1510
|
if (result.hasMetadata) {
|
|
1361
1511
|
try {
|
|
@@ -1367,7 +1517,7 @@ async function detectFastlane(cwd) {
|
|
|
1367
1517
|
}
|
|
1368
1518
|
if (result.hasFastfile) {
|
|
1369
1519
|
try {
|
|
1370
|
-
const content = await
|
|
1520
|
+
const content = await readFile3(fastfilePath, "utf-8");
|
|
1371
1521
|
result.lanes = parseFastfile(content);
|
|
1372
1522
|
if (COMPLEX_RUBY_RE.test(content)) {
|
|
1373
1523
|
result.parseWarnings.push(
|
|
@@ -1380,7 +1530,7 @@ async function detectFastlane(cwd) {
|
|
|
1380
1530
|
}
|
|
1381
1531
|
if (result.hasAppfile) {
|
|
1382
1532
|
try {
|
|
1383
|
-
const content = await
|
|
1533
|
+
const content = await readFile3(appfilePath, "utf-8");
|
|
1384
1534
|
const parsed = parseAppfile(content);
|
|
1385
1535
|
result.packageName = parsed.packageName;
|
|
1386
1536
|
result.jsonKeyPath = parsed.jsonKeyPath;
|
|
@@ -1500,11 +1650,11 @@ async function writeMigrationOutput(result, dir) {
|
|
|
1500
1650
|
await mkdir2(dir, { recursive: true });
|
|
1501
1651
|
const files = [];
|
|
1502
1652
|
if (Object.keys(result.config).length > 0) {
|
|
1503
|
-
const configPath =
|
|
1653
|
+
const configPath = join3(dir, ".gpcrc.json");
|
|
1504
1654
|
await writeFile2(configPath, JSON.stringify(result.config, null, 2) + "\n", "utf-8");
|
|
1505
1655
|
files.push(configPath);
|
|
1506
1656
|
}
|
|
1507
|
-
const migrationPath =
|
|
1657
|
+
const migrationPath = join3(dir, "MIGRATION.md");
|
|
1508
1658
|
const lines = [
|
|
1509
1659
|
"# Fastlane to GPC Migration",
|
|
1510
1660
|
"",
|
|
@@ -2796,17 +2946,17 @@ function watchVitalsWithAutoHalt(reporting, packageName, options) {
|
|
|
2796
2946
|
}
|
|
2797
2947
|
|
|
2798
2948
|
// src/commands/status.ts
|
|
2799
|
-
import { mkdir as mkdir3, readFile as
|
|
2949
|
+
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
2800
2950
|
import { execFile } from "child_process";
|
|
2801
|
-
import { join as
|
|
2951
|
+
import { join as join4 } from "path";
|
|
2802
2952
|
import { getCacheDir } from "@gpc-cli/config";
|
|
2803
2953
|
var DEFAULT_TTL_SECONDS = 3600;
|
|
2804
2954
|
function cacheFilePath(packageName) {
|
|
2805
|
-
return
|
|
2955
|
+
return join4(getCacheDir(), `status-${packageName}.json`);
|
|
2806
2956
|
}
|
|
2807
2957
|
async function loadStatusCache(packageName, ttlSeconds = DEFAULT_TTL_SECONDS) {
|
|
2808
2958
|
try {
|
|
2809
|
-
const raw = await
|
|
2959
|
+
const raw = await readFile4(cacheFilePath(packageName), "utf-8");
|
|
2810
2960
|
const entry = JSON.parse(raw);
|
|
2811
2961
|
const age = (Date.now() - new Date(entry.fetchedAt).getTime()) / 1e3;
|
|
2812
2962
|
if (age > (entry.ttl ?? ttlSeconds)) return null;
|
|
@@ -3251,6 +3401,7 @@ async function runWatchLoop(opts) {
|
|
|
3251
3401
|
const status = await opts.fetch();
|
|
3252
3402
|
await opts.save(status);
|
|
3253
3403
|
console.log(opts.render(status));
|
|
3404
|
+
await opts.onStatus?.(status);
|
|
3254
3405
|
} catch (err) {
|
|
3255
3406
|
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3256
3407
|
}
|
|
@@ -3265,13 +3416,13 @@ async function runWatchLoop(opts) {
|
|
|
3265
3416
|
}
|
|
3266
3417
|
}
|
|
3267
3418
|
function breachStateFilePath(packageName) {
|
|
3268
|
-
return
|
|
3419
|
+
return join4(getCacheDir(), `breach-state-${packageName}.json`);
|
|
3269
3420
|
}
|
|
3270
3421
|
async function trackBreachState(packageName, isBreaching) {
|
|
3271
3422
|
const filePath = breachStateFilePath(packageName);
|
|
3272
3423
|
let prevBreaching = false;
|
|
3273
3424
|
try {
|
|
3274
|
-
const raw = await
|
|
3425
|
+
const raw = await readFile4(filePath, "utf-8");
|
|
3275
3426
|
prevBreaching = JSON.parse(raw).breaching;
|
|
3276
3427
|
} catch {
|
|
3277
3428
|
}
|
|
@@ -3548,8 +3699,8 @@ async function runWatch(client, reporting, config, callbacks) {
|
|
|
3548
3699
|
}
|
|
3549
3700
|
|
|
3550
3701
|
// src/commands/iap.ts
|
|
3551
|
-
import { readdir as readdir3, readFile as
|
|
3552
|
-
import { join as
|
|
3702
|
+
import { readdir as readdir3, readFile as readFile5 } from "fs/promises";
|
|
3703
|
+
import { join as join5 } from "path";
|
|
3553
3704
|
import { paginateAll as paginateAll3 } from "@gpc-cli/api";
|
|
3554
3705
|
async function listInAppProducts(client, packageName, options) {
|
|
3555
3706
|
if (options?.limit || options?.nextPage) {
|
|
@@ -3638,7 +3789,7 @@ async function readProductsFromDir(dir) {
|
|
|
3638
3789
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
3639
3790
|
const localProducts = [];
|
|
3640
3791
|
for (const file of jsonFiles) {
|
|
3641
|
-
const content = await
|
|
3792
|
+
const content = await readFile5(join5(dir, file), "utf-8");
|
|
3642
3793
|
try {
|
|
3643
3794
|
localProducts.push(JSON.parse(content));
|
|
3644
3795
|
} catch {
|
|
@@ -4376,7 +4527,7 @@ async function deleteGrant(client, developerId, email, packageName) {
|
|
|
4376
4527
|
}
|
|
4377
4528
|
|
|
4378
4529
|
// src/commands/testers.ts
|
|
4379
|
-
import { readFile as
|
|
4530
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
4380
4531
|
async function listTesters(client, packageName, track) {
|
|
4381
4532
|
const edit = await client.edits.insert(packageName);
|
|
4382
4533
|
try {
|
|
@@ -4423,7 +4574,7 @@ async function removeTesters(client, packageName, track, groupEmails, commitOpti
|
|
|
4423
4574
|
}
|
|
4424
4575
|
}
|
|
4425
4576
|
async function importTestersFromCsv(client, packageName, track, csvPath, commitOptions) {
|
|
4426
|
-
const content = await
|
|
4577
|
+
const content = await readFile6(csvPath, "utf-8");
|
|
4427
4578
|
const emails = content.split(/[,\n\r]+/).map((e) => e.trim()).filter((e) => e.length > 0 && e.includes("@"));
|
|
4428
4579
|
if (emails.length === 0) {
|
|
4429
4580
|
throw new GpcError(
|
|
@@ -4566,7 +4717,7 @@ async function addRecoveryTargeting(client, packageName, actionId, targeting) {
|
|
|
4566
4717
|
}
|
|
4567
4718
|
|
|
4568
4719
|
// src/commands/data-safety.ts
|
|
4569
|
-
import { readFile as
|
|
4720
|
+
import { readFile as readFile7, stat as stat4 } from "fs/promises";
|
|
4570
4721
|
var MAX_CSV_BYTES = 1 * 1024 * 1024;
|
|
4571
4722
|
async function updateDataSafety(client, packageName, data) {
|
|
4572
4723
|
return client.dataSafety.update(packageName, data);
|
|
@@ -4599,7 +4750,7 @@ async function importDataSafety(client, packageName, filePath) {
|
|
|
4599
4750
|
"The CSV should be a few KB. Check that you are passing the correct file."
|
|
4600
4751
|
);
|
|
4601
4752
|
}
|
|
4602
|
-
const csvContent = await
|
|
4753
|
+
const csvContent = await readFile7(filePath, "utf-8");
|
|
4603
4754
|
return updateDataSafety(client, packageName, { safetyLabels: csvContent });
|
|
4604
4755
|
}
|
|
4605
4756
|
|
|
@@ -4899,16 +5050,16 @@ function createSpinner(message) {
|
|
|
4899
5050
|
}
|
|
4900
5051
|
|
|
4901
5052
|
// src/utils/train-state.ts
|
|
4902
|
-
import { mkdir as mkdir4, readFile as
|
|
4903
|
-
import { dirname, join as
|
|
5053
|
+
import { mkdir as mkdir4, readFile as readFile8, writeFile as writeFile4 } from "fs/promises";
|
|
5054
|
+
import { dirname as dirname2, join as join6 } from "path";
|
|
4904
5055
|
import { getCacheDir as getCacheDir2 } from "@gpc-cli/config";
|
|
4905
5056
|
function stateFile(packageName) {
|
|
4906
|
-
return
|
|
5057
|
+
return join6(getCacheDir2(), `train-${packageName}.json`);
|
|
4907
5058
|
}
|
|
4908
5059
|
async function readTrainState(packageName) {
|
|
4909
5060
|
const path = stateFile(packageName);
|
|
4910
5061
|
try {
|
|
4911
|
-
const raw = await
|
|
5062
|
+
const raw = await readFile8(path, "utf-8");
|
|
4912
5063
|
return JSON.parse(raw);
|
|
4913
5064
|
} catch {
|
|
4914
5065
|
return null;
|
|
@@ -4916,7 +5067,7 @@ async function readTrainState(packageName) {
|
|
|
4916
5067
|
}
|
|
4917
5068
|
async function writeTrainState(packageName, state) {
|
|
4918
5069
|
const path = stateFile(packageName);
|
|
4919
|
-
const dir =
|
|
5070
|
+
const dir = dirname2(path);
|
|
4920
5071
|
await mkdir4(dir, { recursive: true });
|
|
4921
5072
|
await writeFile4(path, JSON.stringify(state, null, 2), "utf-8");
|
|
4922
5073
|
}
|
|
@@ -5062,8 +5213,8 @@ async function executeStage(apiClient, packageName, state, stageIndex) {
|
|
|
5062
5213
|
}
|
|
5063
5214
|
|
|
5064
5215
|
// src/commands/games.ts
|
|
5065
|
-
import { mkdir as mkdir5, readdir as readdir4, readFile as
|
|
5066
|
-
import { extname as extname2, join as
|
|
5216
|
+
import { mkdir as mkdir5, readdir as readdir4, readFile as readFile9, writeFile as writeFile5 } from "fs/promises";
|
|
5217
|
+
import { extname as extname2, join as join7 } from "path";
|
|
5067
5218
|
async function listLeaderboards(client, packageName) {
|
|
5068
5219
|
const result = await client.leaderboards.list(packageName);
|
|
5069
5220
|
return result.items ?? [];
|
|
@@ -5207,7 +5358,7 @@ async function readConfigDir(dir) {
|
|
|
5207
5358
|
const entries = (await readdir4(dir)).filter((f) => f.toLowerCase().endsWith(".json"));
|
|
5208
5359
|
const out = [];
|
|
5209
5360
|
for (const file of entries) {
|
|
5210
|
-
const raw = await
|
|
5361
|
+
const raw = await readFile9(join7(dir, file), "utf-8");
|
|
5211
5362
|
let parsed;
|
|
5212
5363
|
try {
|
|
5213
5364
|
parsed = JSON.parse(raw);
|
|
@@ -5260,7 +5411,7 @@ async function pullAchievementConfigs(client, applicationId, dir) {
|
|
|
5260
5411
|
for (const config of result.items ?? []) {
|
|
5261
5412
|
const id = config.id ?? "unknown";
|
|
5262
5413
|
await writeFile5(
|
|
5263
|
-
|
|
5414
|
+
join7(dir, safeConfigFilename(id)),
|
|
5264
5415
|
JSON.stringify(config, null, 2) + "\n",
|
|
5265
5416
|
"utf-8"
|
|
5266
5417
|
);
|
|
@@ -5299,7 +5450,7 @@ async function pullLeaderboardConfigs(client, applicationId, dir) {
|
|
|
5299
5450
|
for (const config of result.items ?? []) {
|
|
5300
5451
|
const id = config.id ?? "unknown";
|
|
5301
5452
|
await writeFile5(
|
|
5302
|
-
|
|
5453
|
+
join7(dir, safeConfigFilename(id)),
|
|
5303
5454
|
JSON.stringify(config, null, 2) + "\n",
|
|
5304
5455
|
"utf-8"
|
|
5305
5456
|
);
|
|
@@ -5323,8 +5474,8 @@ async function publishEnterpriseApp(client, params) {
|
|
|
5323
5474
|
}
|
|
5324
5475
|
|
|
5325
5476
|
// src/audit.ts
|
|
5326
|
-
import { appendFile, chmod, mkdir as mkdir6, readFile as
|
|
5327
|
-
import { join as
|
|
5477
|
+
import { appendFile, chmod, mkdir as mkdir6, readFile as readFile10, writeFile as writeFile6 } from "fs/promises";
|
|
5478
|
+
import { join as join8 } from "path";
|
|
5328
5479
|
var auditDir = null;
|
|
5329
5480
|
function initAudit(configDir) {
|
|
5330
5481
|
auditDir = configDir;
|
|
@@ -5333,7 +5484,7 @@ async function writeAuditLog(entry) {
|
|
|
5333
5484
|
if (!auditDir) return;
|
|
5334
5485
|
try {
|
|
5335
5486
|
await mkdir6(auditDir, { recursive: true, mode: 448 });
|
|
5336
|
-
const logPath =
|
|
5487
|
+
const logPath = join8(auditDir, "audit.log");
|
|
5337
5488
|
const redactedEntry = redactAuditArgs(entry);
|
|
5338
5489
|
const line = JSON.stringify(redactedEntry) + "\n";
|
|
5339
5490
|
await appendFile(logPath, line, { encoding: "utf-8", mode: 384 });
|
|
@@ -5388,10 +5539,10 @@ function createAuditEntry(command, args, app) {
|
|
|
5388
5539
|
}
|
|
5389
5540
|
async function listAuditEvents(options) {
|
|
5390
5541
|
if (!auditDir) return [];
|
|
5391
|
-
const logPath =
|
|
5542
|
+
const logPath = join8(auditDir, "audit.log");
|
|
5392
5543
|
let content;
|
|
5393
5544
|
try {
|
|
5394
|
-
content = await
|
|
5545
|
+
content = await readFile10(logPath, "utf-8");
|
|
5395
5546
|
} catch {
|
|
5396
5547
|
return [];
|
|
5397
5548
|
}
|
|
@@ -5426,10 +5577,10 @@ async function searchAuditEvents(query) {
|
|
|
5426
5577
|
}
|
|
5427
5578
|
async function clearAuditLog(options) {
|
|
5428
5579
|
if (!auditDir) return { deleted: 0, remaining: 0 };
|
|
5429
|
-
const logPath =
|
|
5580
|
+
const logPath = join8(auditDir, "audit.log");
|
|
5430
5581
|
let content;
|
|
5431
5582
|
try {
|
|
5432
|
-
content = await
|
|
5583
|
+
content = await readFile10(logPath, "utf-8");
|
|
5433
5584
|
} catch {
|
|
5434
5585
|
return { deleted: 0, remaining: 0 };
|
|
5435
5586
|
}
|
|
@@ -5518,7 +5669,7 @@ function safePathWithin(userPath, baseDir) {
|
|
|
5518
5669
|
|
|
5519
5670
|
// src/commands/init.ts
|
|
5520
5671
|
import { mkdir as mkdir7, writeFile as writeFile7, stat as stat5 } from "fs/promises";
|
|
5521
|
-
import { join as
|
|
5672
|
+
import { join as join9 } from "path";
|
|
5522
5673
|
async function exists2(path) {
|
|
5523
5674
|
try {
|
|
5524
5675
|
await stat5(path);
|
|
@@ -5553,7 +5704,7 @@ async function initProject(options) {
|
|
|
5553
5704
|
null,
|
|
5554
5705
|
2
|
|
5555
5706
|
);
|
|
5556
|
-
await safeWrite(
|
|
5707
|
+
await safeWrite(join9(dir, ".gpcrc.json"), gpcrc + "\n", created, skipped, skipExisting);
|
|
5557
5708
|
const preflightrc = JSON.stringify(
|
|
5558
5709
|
{
|
|
5559
5710
|
failOn: "error",
|
|
@@ -5567,27 +5718,27 @@ async function initProject(options) {
|
|
|
5567
5718
|
2
|
|
5568
5719
|
);
|
|
5569
5720
|
await safeWrite(
|
|
5570
|
-
|
|
5721
|
+
join9(dir, ".preflightrc.json"),
|
|
5571
5722
|
preflightrc + "\n",
|
|
5572
5723
|
created,
|
|
5573
5724
|
skipped,
|
|
5574
5725
|
skipExisting
|
|
5575
5726
|
);
|
|
5576
|
-
const metaDir =
|
|
5577
|
-
await safeWrite(
|
|
5578
|
-
await safeWrite(
|
|
5579
|
-
await safeWrite(
|
|
5580
|
-
await safeWrite(
|
|
5581
|
-
const ssDir =
|
|
5727
|
+
const metaDir = join9(dir, "metadata", "android", "en-US");
|
|
5728
|
+
await safeWrite(join9(metaDir, "title.txt"), "", created, skipped, skipExisting);
|
|
5729
|
+
await safeWrite(join9(metaDir, "short_description.txt"), "", created, skipped, skipExisting);
|
|
5730
|
+
await safeWrite(join9(metaDir, "full_description.txt"), "", created, skipped, skipExisting);
|
|
5731
|
+
await safeWrite(join9(metaDir, "video.txt"), "", created, skipped, skipExisting);
|
|
5732
|
+
const ssDir = join9(metaDir, "images", "phoneScreenshots");
|
|
5582
5733
|
await mkdir7(ssDir, { recursive: true });
|
|
5583
|
-
await safeWrite(
|
|
5734
|
+
await safeWrite(join9(ssDir, ".gitkeep"), "", created, skipped, skipExisting);
|
|
5584
5735
|
if (ci === "github") {
|
|
5585
5736
|
const workflow = githubActionsTemplate(pkg);
|
|
5586
|
-
const workflowDir =
|
|
5587
|
-
await safeWrite(
|
|
5737
|
+
const workflowDir = join9(dir, ".github", "workflows");
|
|
5738
|
+
await safeWrite(join9(workflowDir, "gpc-release.yml"), workflow, created, skipped, skipExisting);
|
|
5588
5739
|
} else if (ci === "gitlab") {
|
|
5589
5740
|
const pipeline = gitlabCiTemplate(pkg);
|
|
5590
|
-
await safeWrite(
|
|
5741
|
+
await safeWrite(join9(dir, ".gitlab-ci-gpc.yml"), pipeline, created, skipped, skipExisting);
|
|
5591
5742
|
}
|
|
5592
5743
|
return { created, skipped };
|
|
5593
5744
|
}
|
|
@@ -5683,13 +5834,13 @@ var DEFAULT_PREFLIGHT_CONFIG = {
|
|
|
5683
5834
|
};
|
|
5684
5835
|
|
|
5685
5836
|
// src/preflight/config.ts
|
|
5686
|
-
import { readFile as
|
|
5837
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
5687
5838
|
var VALID_SEVERITIES = /* @__PURE__ */ new Set(["critical", "error", "warning", "info"]);
|
|
5688
5839
|
async function loadPreflightConfig(configPath) {
|
|
5689
5840
|
const path = configPath || ".preflightrc.json";
|
|
5690
5841
|
let raw;
|
|
5691
5842
|
try {
|
|
5692
|
-
raw = await
|
|
5843
|
+
raw = await readFile11(path, "utf-8");
|
|
5693
5844
|
} catch {
|
|
5694
5845
|
return { ...DEFAULT_PREFLIGHT_CONFIG };
|
|
5695
5846
|
}
|
|
@@ -5734,7 +5885,7 @@ async function loadPreflightConfig(configPath) {
|
|
|
5734
5885
|
}
|
|
5735
5886
|
|
|
5736
5887
|
// src/preflight/aab-reader.ts
|
|
5737
|
-
import { readFile as
|
|
5888
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
5738
5889
|
import { fromBuffer as fromBuffer2 } from "yauzl";
|
|
5739
5890
|
|
|
5740
5891
|
// src/preflight/manifest-parser.ts
|
|
@@ -5932,7 +6083,7 @@ async function readAab(aabPath) {
|
|
|
5932
6083
|
const manifestPath = detectManifestPath(aabPath);
|
|
5933
6084
|
let buffer;
|
|
5934
6085
|
try {
|
|
5935
|
-
buffer = await
|
|
6086
|
+
buffer = await readFile12(aabPath);
|
|
5936
6087
|
} catch (err) {
|
|
5937
6088
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5938
6089
|
throw new Error(`Could not read ${aabPath}: ${msg}`, { cause: err });
|
|
@@ -6656,8 +6807,8 @@ ${fileList}` + (hasPageSizeCompat ? "\nandroid:pageSizeCompat is set, so the app
|
|
|
6656
6807
|
}
|
|
6657
6808
|
|
|
6658
6809
|
// src/preflight/scanners/metadata-scanner.ts
|
|
6659
|
-
import { readdir as readdir5, stat as stat6, readFile as
|
|
6660
|
-
import { join as
|
|
6810
|
+
import { readdir as readdir5, stat as stat6, readFile as readFile13 } from "fs/promises";
|
|
6811
|
+
import { join as join10 } from "path";
|
|
6661
6812
|
var SAFE_LANG = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/;
|
|
6662
6813
|
var FILE_MAP2 = {
|
|
6663
6814
|
"title.txt": "title",
|
|
@@ -6708,14 +6859,14 @@ var metadataScanner = {
|
|
|
6708
6859
|
return findings;
|
|
6709
6860
|
}
|
|
6710
6861
|
for (const lang of locales) {
|
|
6711
|
-
const langDir =
|
|
6862
|
+
const langDir = join10(dir, lang);
|
|
6712
6863
|
const langStat = await stat6(langDir).catch(() => null);
|
|
6713
6864
|
if (!langStat?.isDirectory()) continue;
|
|
6714
6865
|
const fields = {};
|
|
6715
6866
|
for (const [fileName, field] of Object.entries(FILE_MAP2)) {
|
|
6716
|
-
const filePath =
|
|
6867
|
+
const filePath = join10(langDir, fileName);
|
|
6717
6868
|
try {
|
|
6718
|
-
const content = await
|
|
6869
|
+
const content = await readFile13(filePath, "utf-8");
|
|
6719
6870
|
fields[field] = content.trimEnd();
|
|
6720
6871
|
} catch {
|
|
6721
6872
|
}
|
|
@@ -6754,7 +6905,7 @@ var metadataScanner = {
|
|
|
6754
6905
|
let totalScreenshots = 0;
|
|
6755
6906
|
let phoneScreenshots = 0;
|
|
6756
6907
|
for (const ssDir of SCREENSHOT_DIRS) {
|
|
6757
|
-
const ssPath =
|
|
6908
|
+
const ssPath = join10(langDir, "images", ssDir);
|
|
6758
6909
|
try {
|
|
6759
6910
|
const ssEntries = await readdir5(ssPath);
|
|
6760
6911
|
const imageFiles = ssEntries.filter((f) => /\.(png|jpe?g|webp)$/i.test(f));
|
|
@@ -6786,9 +6937,9 @@ var metadataScanner = {
|
|
|
6786
6937
|
}
|
|
6787
6938
|
}
|
|
6788
6939
|
const defaultLang = locales.includes("en-US") ? "en-US" : locales[0];
|
|
6789
|
-
const privacyPath =
|
|
6940
|
+
const privacyPath = join10(dir, defaultLang, "privacy_policy_url.txt");
|
|
6790
6941
|
try {
|
|
6791
|
-
const url = await
|
|
6942
|
+
const url = await readFile13(privacyPath, "utf-8");
|
|
6792
6943
|
if (!url.trim()) throw new Error("empty");
|
|
6793
6944
|
} catch {
|
|
6794
6945
|
findings.push({
|
|
@@ -6813,11 +6964,11 @@ var metadataScanner = {
|
|
|
6813
6964
|
};
|
|
6814
6965
|
|
|
6815
6966
|
// src/preflight/scanners/secrets-scanner.ts
|
|
6816
|
-
import { readFile as
|
|
6967
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
6817
6968
|
|
|
6818
6969
|
// src/preflight/scan-files.ts
|
|
6819
6970
|
import { readdir as readdir6, stat as stat7 } from "fs/promises";
|
|
6820
|
-
import { join as
|
|
6971
|
+
import { join as join11, extname as extname3 } from "path";
|
|
6821
6972
|
var DEFAULT_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
6822
6973
|
".git",
|
|
6823
6974
|
"node_modules",
|
|
@@ -6840,7 +6991,7 @@ async function collectSourceFiles(dir, extensions, skipDirs = DEFAULT_SKIP_DIRS,
|
|
|
6840
6991
|
}
|
|
6841
6992
|
for (const entry of entries) {
|
|
6842
6993
|
if (skipDirs.has(entry)) continue;
|
|
6843
|
-
const fullPath =
|
|
6994
|
+
const fullPath = join11(dir, entry);
|
|
6844
6995
|
const s = await stat7(fullPath).catch(() => null);
|
|
6845
6996
|
if (!s) continue;
|
|
6846
6997
|
if (s.isDirectory()) {
|
|
@@ -6933,7 +7084,7 @@ var secretsScanner = {
|
|
|
6933
7084
|
for (const filePath of files) {
|
|
6934
7085
|
let content;
|
|
6935
7086
|
try {
|
|
6936
|
-
content = await
|
|
7087
|
+
content = await readFile14(filePath, "utf-8");
|
|
6937
7088
|
} catch {
|
|
6938
7089
|
continue;
|
|
6939
7090
|
}
|
|
@@ -6961,7 +7112,7 @@ var secretsScanner = {
|
|
|
6961
7112
|
};
|
|
6962
7113
|
|
|
6963
7114
|
// src/preflight/scanners/billing-scanner.ts
|
|
6964
|
-
import { readFile as
|
|
7115
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
6965
7116
|
var BILLING_PATTERNS = [
|
|
6966
7117
|
{
|
|
6967
7118
|
ruleId: "billing-stripe-sdk",
|
|
@@ -7022,7 +7173,7 @@ var billingScanner = {
|
|
|
7022
7173
|
for (const filePath of files) {
|
|
7023
7174
|
let content;
|
|
7024
7175
|
try {
|
|
7025
|
-
content = await
|
|
7176
|
+
content = await readFile15(filePath, "utf-8");
|
|
7026
7177
|
} catch {
|
|
7027
7178
|
continue;
|
|
7028
7179
|
}
|
|
@@ -7048,7 +7199,7 @@ var billingScanner = {
|
|
|
7048
7199
|
};
|
|
7049
7200
|
|
|
7050
7201
|
// src/preflight/scanners/privacy-scanner.ts
|
|
7051
|
-
import { readFile as
|
|
7202
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
7052
7203
|
var TRACKING_SDKS = [
|
|
7053
7204
|
{
|
|
7054
7205
|
name: "Facebook SDK",
|
|
@@ -7088,7 +7239,7 @@ var privacyScanner = {
|
|
|
7088
7239
|
for (const filePath of files) {
|
|
7089
7240
|
let content;
|
|
7090
7241
|
try {
|
|
7091
|
-
content = await
|
|
7242
|
+
content = await readFile16(filePath, "utf-8");
|
|
7092
7243
|
} catch {
|
|
7093
7244
|
continue;
|
|
7094
7245
|
}
|
|
@@ -7238,6 +7389,19 @@ var policyScanner = {
|
|
|
7238
7389
|
suggestion: "Run `gpc verify checklist` to review readiness, including registration of every app on your account. This is a distribution-market and account-registration reminder, not an AAB/APK defect.",
|
|
7239
7390
|
policyUrl: "https://developer.android.com/developer-verification"
|
|
7240
7391
|
});
|
|
7392
|
+
const foregroundServicePerms = manifest.permissions.filter((p) => p.startsWith("android.permission.FOREGROUND_SERVICE")).sort();
|
|
7393
|
+
if (foregroundServicePerms.length > 0) {
|
|
7394
|
+
const shortNames = foregroundServicePerms.map((p) => p.replace("android.permission.", ""));
|
|
7395
|
+
findings.push({
|
|
7396
|
+
scanner: "policy",
|
|
7397
|
+
ruleId: "policy-app-content-declaration",
|
|
7398
|
+
severity: "info",
|
|
7399
|
+
title: "Foreground service permissions require an App content declaration",
|
|
7400
|
+
message: `This app requests ${shortNames.join(", ")}. Google Play requires a completed "Foreground service permissions" declaration under App content before it will accept a release. The declaration is stored in Play Console, so it cannot be verified from the AAB.`,
|
|
7401
|
+
suggestion: "Complete Play Console \u2192 your app \u2192 Policy \u2192 App content \u2192 Foreground service permissions before releasing. If it is incomplete, the Play API rejects the upload with a 403 whose wording resembles a service account permission error; GPC reports that as API_DECLARATION_REQUIRED.",
|
|
7402
|
+
policyUrl: "https://support.google.com/googleplay/android-developer/answer/13392821"
|
|
7403
|
+
});
|
|
7404
|
+
}
|
|
7241
7405
|
if (perms.has("android.permission.SYSTEM_ALERT_WINDOW")) {
|
|
7242
7406
|
findings.push({
|
|
7243
7407
|
scanner: "policy",
|
|
@@ -7450,13 +7614,13 @@ async function runPreflight(options) {
|
|
|
7450
7614
|
|
|
7451
7615
|
// src/commands/plugin-scaffold.ts
|
|
7452
7616
|
import { mkdir as mkdir8, writeFile as writeFile8 } from "fs/promises";
|
|
7453
|
-
import { join as
|
|
7617
|
+
import { join as join12 } from "path";
|
|
7454
7618
|
async function scaffoldPlugin(options) {
|
|
7455
7619
|
const { name, dir, description = `GPC plugin: ${name}` } = options;
|
|
7456
7620
|
const pluginName = name.startsWith("gpc-plugin-") ? name : `gpc-plugin-${name}`;
|
|
7457
7621
|
const shortName = pluginName.replace(/^gpc-plugin-/, "");
|
|
7458
|
-
const srcDir =
|
|
7459
|
-
const testDir =
|
|
7622
|
+
const srcDir = join12(dir, "src");
|
|
7623
|
+
const testDir = join12(dir, "tests");
|
|
7460
7624
|
await mkdir8(srcDir, { recursive: true });
|
|
7461
7625
|
await mkdir8(testDir, { recursive: true });
|
|
7462
7626
|
const files = [];
|
|
@@ -7474,6 +7638,9 @@ async function scaffoldPlugin(options) {
|
|
|
7474
7638
|
}
|
|
7475
7639
|
},
|
|
7476
7640
|
files: ["dist"],
|
|
7641
|
+
gpc: {
|
|
7642
|
+
permissions: ["hooks:beforeCommand", "hooks:afterCommand"]
|
|
7643
|
+
},
|
|
7477
7644
|
scripts: {
|
|
7478
7645
|
build: "tsup src/index.ts --format esm --dts",
|
|
7479
7646
|
dev: "tsup src/index.ts --format esm --dts --watch",
|
|
@@ -7492,7 +7659,7 @@ async function scaffoldPlugin(options) {
|
|
|
7492
7659
|
vitest: "^3.0.0"
|
|
7493
7660
|
}
|
|
7494
7661
|
};
|
|
7495
|
-
await writeFile8(
|
|
7662
|
+
await writeFile8(join12(dir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
|
|
7496
7663
|
files.push("package.json");
|
|
7497
7664
|
const tsconfig = {
|
|
7498
7665
|
compilerOptions: {
|
|
@@ -7508,7 +7675,7 @@ async function scaffoldPlugin(options) {
|
|
|
7508
7675
|
},
|
|
7509
7676
|
include: ["src"]
|
|
7510
7677
|
};
|
|
7511
|
-
await writeFile8(
|
|
7678
|
+
await writeFile8(join12(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
|
|
7512
7679
|
files.push("tsconfig.json");
|
|
7513
7680
|
const srcContent = `import { definePlugin } from "@gpc-cli/plugin-sdk";
|
|
7514
7681
|
import type { CommandEvent, CommandResult } from "@gpc-cli/plugin-sdk";
|
|
@@ -7528,7 +7695,8 @@ export const plugin = definePlugin({
|
|
|
7528
7695
|
// Example: send notifications, update dashboards, etc.
|
|
7529
7696
|
});
|
|
7530
7697
|
|
|
7531
|
-
//
|
|
7698
|
+
// To add custom commands, first add "commands:register" to
|
|
7699
|
+
// package.json#gpc.permissions, then uncomment this block:
|
|
7532
7700
|
// hooks.registerCommands((registry) => {
|
|
7533
7701
|
// registry.add({
|
|
7534
7702
|
// name: "${shortName}",
|
|
@@ -7541,7 +7709,7 @@ export const plugin = definePlugin({
|
|
|
7541
7709
|
},
|
|
7542
7710
|
});
|
|
7543
7711
|
`;
|
|
7544
|
-
await writeFile8(
|
|
7712
|
+
await writeFile8(join12(srcDir, "index.ts"), srcContent);
|
|
7545
7713
|
files.push("src/index.ts");
|
|
7546
7714
|
const testContent = `import { describe, it, expect, vi } from "vitest";
|
|
7547
7715
|
import { plugin } from "../src/index";
|
|
@@ -7566,7 +7734,7 @@ describe("${pluginName}", () => {
|
|
|
7566
7734
|
});
|
|
7567
7735
|
});
|
|
7568
7736
|
`;
|
|
7569
|
-
await writeFile8(
|
|
7737
|
+
await writeFile8(join12(testDir, "plugin.test.ts"), testContent);
|
|
7570
7738
|
files.push("tests/plugin.test.ts");
|
|
7571
7739
|
return { dir, files };
|
|
7572
7740
|
}
|
|
@@ -7757,7 +7925,7 @@ async function downloadGeneratedApk(client, packageName, versionCode, apkId, out
|
|
|
7757
7925
|
}
|
|
7758
7926
|
|
|
7759
7927
|
// src/commands/bundle-analysis.ts
|
|
7760
|
-
import { readFile as
|
|
7928
|
+
import { readFile as readFile17, stat as stat8 } from "fs/promises";
|
|
7761
7929
|
var EOCD_SIGNATURE = 101010256;
|
|
7762
7930
|
var CD_SIGNATURE = 33639248;
|
|
7763
7931
|
var MODULE_SUBDIRS = /* @__PURE__ */ new Set(["dex", "manifest", "res", "assets", "lib", "resources.pb", "root"]);
|
|
@@ -7837,7 +8005,7 @@ async function analyzeBundle(filePath) {
|
|
|
7837
8005
|
if (!fileInfo || !fileInfo.isFile()) {
|
|
7838
8006
|
throw new Error(`File not found: ${filePath}`);
|
|
7839
8007
|
}
|
|
7840
|
-
const buf = await
|
|
8008
|
+
const buf = await readFile17(filePath);
|
|
7841
8009
|
const cdEntries = parseCentralDirectory(buf);
|
|
7842
8010
|
const fileType = detectFileType2(filePath);
|
|
7843
8011
|
const isAab = fileType === "aab";
|
|
@@ -7923,7 +8091,7 @@ function topFiles(analysis, n = 20) {
|
|
|
7923
8091
|
async function checkBundleSize(analysis, configPath = ".bundlesize.json") {
|
|
7924
8092
|
let config;
|
|
7925
8093
|
try {
|
|
7926
|
-
const raw = await
|
|
8094
|
+
const raw = await readFile17(configPath, "utf-8");
|
|
7927
8095
|
config = JSON.parse(raw);
|
|
7928
8096
|
} catch {
|
|
7929
8097
|
return { passed: true, violations: [] };
|
|
@@ -9285,7 +9453,7 @@ async function sha256File(filePath) {
|
|
|
9285
9453
|
|
|
9286
9454
|
// src/commands/image-sync.ts
|
|
9287
9455
|
import { readdir as readdir7, stat as stat10 } from "fs/promises";
|
|
9288
|
-
import { join as
|
|
9456
|
+
import { join as join13, extname as extname5 } from "path";
|
|
9289
9457
|
import { PlayApiError } from "@gpc-cli/api";
|
|
9290
9458
|
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg"]);
|
|
9291
9459
|
var ALL_IMAGE_TYPES2 = [
|
|
@@ -9340,7 +9508,7 @@ async function syncImages(client, packageName, dir, options) {
|
|
|
9340
9508
|
try {
|
|
9341
9509
|
for (const language of languages) {
|
|
9342
9510
|
for (const imageType of imageTypes) {
|
|
9343
|
-
const localDir =
|
|
9511
|
+
const localDir = join13(dir, language, imageType);
|
|
9344
9512
|
const localFiles = await scanLocalImages(localDir);
|
|
9345
9513
|
let remoteImages;
|
|
9346
9514
|
try {
|
|
@@ -9355,12 +9523,12 @@ async function syncImages(client, packageName, dir, options) {
|
|
|
9355
9523
|
const remoteSha256Set = new Set(remoteImages.map((img) => img.sha256.toLowerCase()));
|
|
9356
9524
|
const localHashes = /* @__PURE__ */ new Map();
|
|
9357
9525
|
for (const file of localFiles) {
|
|
9358
|
-
const hash = await sha256File(
|
|
9526
|
+
const hash = await sha256File(join13(localDir, file));
|
|
9359
9527
|
localHashes.set(file, hash);
|
|
9360
9528
|
}
|
|
9361
9529
|
const uploadLocal = async (file, reason) => {
|
|
9362
9530
|
if (!options?.dryRun) {
|
|
9363
|
-
const filePath =
|
|
9531
|
+
const filePath = join13(localDir, file);
|
|
9364
9532
|
const check = await validateImage(filePath, imageType);
|
|
9365
9533
|
if (!check.valid) {
|
|
9366
9534
|
throw new GpcError(
|
|
@@ -9394,7 +9562,13 @@ async function syncImages(client, packageName, dir, options) {
|
|
|
9394
9562
|
if (inOrder) {
|
|
9395
9563
|
for (const file of localFiles) {
|
|
9396
9564
|
skipped++;
|
|
9397
|
-
details.push({
|
|
9565
|
+
details.push({
|
|
9566
|
+
language,
|
|
9567
|
+
imageType,
|
|
9568
|
+
file,
|
|
9569
|
+
action: "skip",
|
|
9570
|
+
reason: "already in order"
|
|
9571
|
+
});
|
|
9398
9572
|
}
|
|
9399
9573
|
continue;
|
|
9400
9574
|
}
|
|
@@ -9404,7 +9578,13 @@ async function syncImages(client, packageName, dir, options) {
|
|
|
9404
9578
|
}
|
|
9405
9579
|
for (const img of remoteImages) {
|
|
9406
9580
|
deleted++;
|
|
9407
|
-
details.push({
|
|
9581
|
+
details.push({
|
|
9582
|
+
language,
|
|
9583
|
+
imageType,
|
|
9584
|
+
file: img.id,
|
|
9585
|
+
action: "delete",
|
|
9586
|
+
reason: "reordering"
|
|
9587
|
+
});
|
|
9408
9588
|
}
|
|
9409
9589
|
}
|
|
9410
9590
|
for (const file of localFiles) {
|
|
@@ -9831,6 +10011,7 @@ export {
|
|
|
9831
10011
|
diffOneTimeProduct,
|
|
9832
10012
|
diffReleases,
|
|
9833
10013
|
diffSubscription,
|
|
10014
|
+
discoverPluginEntries,
|
|
9834
10015
|
discoverPlugins,
|
|
9835
10016
|
dispatchWebhook,
|
|
9836
10017
|
downloadFinancialReport,
|
|
@@ -10041,6 +10222,7 @@ export {
|
|
|
10041
10222
|
validateImage,
|
|
10042
10223
|
validateLanguageCode,
|
|
10043
10224
|
validatePackageName,
|
|
10225
|
+
validatePluginForApproval,
|
|
10044
10226
|
validatePreSubmission,
|
|
10045
10227
|
validateReleaseNotes,
|
|
10046
10228
|
validateSku,
|