codeplay-common 4.9.4 → 4.9.6
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.
|
@@ -4448,6 +4448,21 @@ function syncAndroidBuildOptimization() {
|
|
|
4448
4448
|
}
|
|
4449
4449
|
// Android build optimization END
|
|
4450
4450
|
|
|
4451
|
+
function syncAndroidCordovaResourceKeep() {
|
|
4452
|
+
if (buildPlatform !== 'android' || !fs.existsSync(androidPlatformPath)) return;
|
|
4453
|
+
|
|
4454
|
+
// Cordova loads config.xml by name; preserve it even when no plugins are installed yet.
|
|
4455
|
+
const rawPath = path.join(androidPlatformPath, 'app', 'src', 'main', 'res', 'raw');
|
|
4456
|
+
const keepPath = path.join(rawPath, 'codeplay_cordova_keep.xml');
|
|
4457
|
+
const content = '<?xml version="1.0" encoding="utf-8"?>\n'
|
|
4458
|
+
+ '<resources xmlns:tools="http://schemas.android.com/tools"\n'
|
|
4459
|
+
+ ' tools:keep="@xml/config" />\n';
|
|
4460
|
+
if (fs.existsSync(keepPath) && fs.readFileSync(keepPath, 'utf8') === content) return;
|
|
4461
|
+
|
|
4462
|
+
fs.mkdirSync(rawPath, { recursive: true });
|
|
4463
|
+
fs.writeFileSync(keepPath, content, 'utf8');
|
|
4464
|
+
}
|
|
4465
|
+
|
|
4451
4466
|
async function main() {
|
|
4452
4467
|
validateThemeFolderLocation();
|
|
4453
4468
|
validateAndRestoreSignDetails();
|
|
@@ -4467,6 +4482,7 @@ async function main() {
|
|
|
4467
4482
|
checkAdmobConfigurationProperty();
|
|
4468
4483
|
syncAndroidAdIdPermission();
|
|
4469
4484
|
syncAndroidBuildOptimization();
|
|
4485
|
+
syncAndroidCordovaResourceKeep();
|
|
4470
4486
|
}
|
|
4471
4487
|
|
|
4472
4488
|
await main();
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
//node take-screen-image.js --profiles vivo-store //only taking screenshot for vivo-store - Not only vivo, we can do this to all of our store images.
|
|
4
4
|
//node take-screen-image.js --vivo-existing //Used if we already took the screenshot and those vivo screenshots are not there then execute this to resize the screenshot for vivo store.
|
|
5
5
|
//node take-screen-image.js --oppo-existing //Used if we already took the screenshot and those oppo screenshots are not there then execute this to resize the screenshot for Oppo store.
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
//App Store: import actual iOS/iPadOS PNG captures; Android replay is not an Apple screenshot source.
|
|
7
|
+
//node take-screen-image.js --apple-source "C:\captures\iphone" --profiles iphone
|
|
8
|
+
//Add --apple-promotional to reuse screenshot-copy.json captions around the original capture.
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
/*
|
|
@@ -74,6 +74,22 @@ const browserExecutablePaths = [
|
|
|
74
74
|
'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
|
|
75
75
|
];
|
|
76
76
|
|
|
77
|
+
// Full-display sizes from Apple's screenshot specifications, checked 2026-10-20.
|
|
78
|
+
// https://developer.apple.com/help/app-store-connect/reference/app-information/screenshot-specifications/
|
|
79
|
+
// Source captures retain their size; never resize one Apple device family into another.
|
|
80
|
+
const appleScreenshotSizes = {
|
|
81
|
+
iphone: [
|
|
82
|
+
[1260, 2736], [1290, 2796], [1320, 2868], [1284, 2778], [1242, 2688],
|
|
83
|
+
[1179, 2556], [1206, 2622], [1170, 2532], [1125, 2436], [1080, 2340],
|
|
84
|
+
[1242, 2208], [750, 1334], [640, 1136], [640, 960],
|
|
85
|
+
],
|
|
86
|
+
'iphone-duo': [[1398, 2034], [2007, 2853]],
|
|
87
|
+
ipad: [
|
|
88
|
+
[2064, 2752], [2048, 2732], [1488, 2266], [1668, 2420],
|
|
89
|
+
[1668, 2388], [1640, 2360], [1668, 2224], [1536, 2048], [768, 1024],
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
|
|
77
93
|
const deviceProfiles = [
|
|
78
94
|
{
|
|
79
95
|
name: 'android-phone',
|
|
@@ -520,12 +536,25 @@ function parseArguments() {
|
|
|
520
536
|
screenshots: '',
|
|
521
537
|
androidFrame: '',
|
|
522
538
|
iosFrame: '',
|
|
539
|
+
appleSource: '',
|
|
540
|
+
applePromotional: false,
|
|
541
|
+
appleExisting: false,
|
|
523
542
|
};
|
|
524
543
|
|
|
525
544
|
for (let index = 0; index < args.length; index += 1) {
|
|
526
545
|
const argument = args[index];
|
|
527
546
|
|
|
528
|
-
if (argument === '--
|
|
547
|
+
if (argument === '--apple-existing') {
|
|
548
|
+
options.appleExisting = true;
|
|
549
|
+
} else if (argument === '--apple-source') {
|
|
550
|
+
options.appleSource = args[index + 1] || '';
|
|
551
|
+
if (!options.appleSource || options.appleSource.startsWith('--')) {
|
|
552
|
+
throw new Error('--apple-source requires a directory of genuine iOS/iPadOS PNG captures.');
|
|
553
|
+
}
|
|
554
|
+
index += 1;
|
|
555
|
+
} else if (argument === '--apple-promotional') {
|
|
556
|
+
options.applePromotional = true;
|
|
557
|
+
} else if (argument === '--amazon-assets') {
|
|
529
558
|
options.amazonAssets = true;
|
|
530
559
|
} else if (argument === '--samsung-assets') {
|
|
531
560
|
options.samsungAssets = true;
|
|
@@ -588,6 +617,9 @@ function printProfiles() {
|
|
|
588
617
|
+ `(folder ${profile.outputDirectory}, viewport ${profile.width}x${profile.height}, `
|
|
589
618
|
+ `${(profile.outputFormat || 'png').toUpperCase()} ${outputWidth}x${outputHeight}${framedSize})`,
|
|
590
619
|
);
|
|
620
|
+
if (isAppleProfile(profile)) {
|
|
621
|
+
console.log(' Native Apple capture required; Android replay/reframing is disabled.');
|
|
622
|
+
}
|
|
591
623
|
if (profile.storeRequirements) {
|
|
592
624
|
console.log(
|
|
593
625
|
` Store limits: ${profile.storeRequirements.minImages}-${profile.storeRequirements.maxImages} images, `
|
|
@@ -603,12 +635,14 @@ function printHelp() {
|
|
|
603
635
|
console.log('');
|
|
604
636
|
console.log('Usage:');
|
|
605
637
|
console.log(' node take-screen-image.js');
|
|
606
|
-
console.log(' node take-screen-image.js --profiles iphone
|
|
607
|
-
console.log(' node take-screen-image.js --profiles
|
|
608
|
-
console.log(' node take-screen-image.js --
|
|
609
|
-
console.log(' node take-screen-image.js --
|
|
638
|
+
console.log(' node take-screen-image.js --apple-source "C:\\captures\\iphone" --profiles iphone');
|
|
639
|
+
console.log(' node take-screen-image.js --apple-source "C:\\captures\\ipad" --profiles ipad --apple-promotional');
|
|
640
|
+
console.log(' node take-screen-image.js --apple-source "C:\\captures\\duo" --profiles iphone-duo');
|
|
641
|
+
console.log(' node take-screen-image.js --apple-existing --profiles iphone,iphone-duo,ipad');
|
|
642
|
+
console.log(' node take-screen-image.js --android-frame graphite');
|
|
643
|
+
console.log(' node take-screen-image.js --prefix home --profiles android-phone');
|
|
610
644
|
console.log(' node take-screen-image.js --frame-existing');
|
|
611
|
-
console.log(' node take-screen-image.js --reframe-existing --profiles android-phone
|
|
645
|
+
console.log(' node take-screen-image.js --reframe-existing --profiles android-phone');
|
|
612
646
|
console.log(' node take-screen-image.js --reframe-existing --screenshots 1,2');
|
|
613
647
|
console.log(' node take-screen-image.js --refresh-info 1');
|
|
614
648
|
console.log(' node take-screen-image.js --vivo-existing');
|
|
@@ -620,7 +654,15 @@ function printHelp() {
|
|
|
620
654
|
console.log(' node take-screen-image.js --delete 3,4,5');
|
|
621
655
|
console.log('');
|
|
622
656
|
console.log('A normal capture automatically creates any missing Amazon, Samsung, and Play Store assets.');
|
|
623
|
-
console.log('Frame
|
|
657
|
+
console.log('Frame default: Android graphite (A). Apple imports do not add a device frame.');
|
|
658
|
+
console.log('iPhone/iPad profiles require --apple-source and exactly one profile per import.');
|
|
659
|
+
console.log('Name captures 1.png, 2.png, etc. to reuse existing promotional copy.');
|
|
660
|
+
console.log('Apple originals and optional promotions are saved separately under Store-upload/Apple-Native.');
|
|
661
|
+
console.log('Imports preserve source dimensions and system bars; the tool cannot verify capture provenance.');
|
|
662
|
+
console.log('Use full-display PNG captures without alpha channels, up to 10 per size.');
|
|
663
|
+
console.log('Each size gets its own folder. Re-importing a filename at that size never overwrites it.');
|
|
664
|
+
console.log('--apple-existing reprocesses Store-image-Screenshot images without synthetic system bars or frames.');
|
|
665
|
+
console.log('Reprocessed images go to Store-upload/Apple-Reprocessed; they are not native Apple captures.');
|
|
624
666
|
}
|
|
625
667
|
|
|
626
668
|
async function chooseFrameStyles(options) {
|
|
@@ -634,7 +676,7 @@ async function chooseFrameStyles(options) {
|
|
|
634
676
|
const terminal = canPrompt ? readline.createInterface({ input: process.stdin, output: process.stdout }) : null;
|
|
635
677
|
|
|
636
678
|
try {
|
|
637
|
-
for (const platform of ['android'
|
|
679
|
+
for (const platform of ['android']) {
|
|
638
680
|
if (!choices[platform] && terminal) {
|
|
639
681
|
const names = Object.values(frameStyles[platform]).map((style) => style.label);
|
|
640
682
|
choices[platform] = await terminal.question(
|
|
@@ -651,7 +693,7 @@ async function chooseFrameStyles(options) {
|
|
|
651
693
|
if (terminal) terminal.close();
|
|
652
694
|
}
|
|
653
695
|
|
|
654
|
-
console.log(`Frame selection: Android ${choices.android}
|
|
696
|
+
console.log(`Frame selection: Android ${choices.android}. Apple captures keep their native appearance.`);
|
|
655
697
|
return choices;
|
|
656
698
|
}
|
|
657
699
|
|
|
@@ -1732,6 +1774,8 @@ async function waitForReplayLayout(client, snapshot) {
|
|
|
1732
1774
|
}
|
|
1733
1775
|
|
|
1734
1776
|
function getFrameAdornment(frameType) {
|
|
1777
|
+
if (frameType === 'native-apple') return '';
|
|
1778
|
+
|
|
1735
1779
|
if (frameType === 'iphone') {
|
|
1736
1780
|
return '';
|
|
1737
1781
|
}
|
|
@@ -2135,6 +2179,7 @@ function createDeviceFrameHtml(
|
|
|
2135
2179
|
) {
|
|
2136
2180
|
const screenshotDataUrl = `data:image/${imageFormat === 'jpeg' ? 'jpeg' : 'png'};base64,${screenshotBuffer.toString('base64')}`;
|
|
2137
2181
|
const aspectRatio = `${imageWidth} / ${imageHeight}`;
|
|
2182
|
+
const useDuoFrame = profile.name === 'iphone-duo' && profile.frameType !== 'native-apple';
|
|
2138
2183
|
const phonePlatform = profile.frameType === 'iphone' ? 'ios' : profile.frameType === 'android-phone' ? 'android' : null;
|
|
2139
2184
|
const statusBarPlatform = profile.frameType === 'ipad' ? 'ipad' : phonePlatform;
|
|
2140
2185
|
const phoneStyle = phonePlatform && frameStyles[phonePlatform][selectedFrameStyles[phonePlatform]];
|
|
@@ -2265,7 +2310,7 @@ function createDeviceFrameHtml(
|
|
|
2265
2310
|
border-radius: 999px;
|
|
2266
2311
|
background: #000;
|
|
2267
2312
|
}
|
|
2268
|
-
${
|
|
2313
|
+
${useDuoFrame ? `
|
|
2269
2314
|
.device.iphone-duo {
|
|
2270
2315
|
--duo-radius: clamp(24px, 4vw, 100px);
|
|
2271
2316
|
--duo-bezel: clamp(5px, 0.65vw, 14px);
|
|
@@ -2614,10 +2659,30 @@ function createDeviceFrameHtml(
|
|
|
2614
2659
|
</style>
|
|
2615
2660
|
</head>
|
|
2616
2661
|
<body class="${phonePlatform ? 'phone-frame' : ''} ${screenshotCopy ? 'has-copy' : ''}">
|
|
2662
|
+
${profile.frameType === 'native-apple' ? `<style>
|
|
2663
|
+
.device.native-apple {
|
|
2664
|
+
box-sizing: border-box;
|
|
2665
|
+
width: auto;
|
|
2666
|
+
height: 68%;
|
|
2667
|
+
padding: 0;
|
|
2668
|
+
border: 0;
|
|
2669
|
+
border-radius: 0;
|
|
2670
|
+
background: transparent;
|
|
2671
|
+
box-shadow: 0 2vh 5vh rgba(0,0,0,0.2);
|
|
2672
|
+
}
|
|
2673
|
+
.device.native-apple .screen { border-radius: 0; box-shadow: none; }
|
|
2674
|
+
.device.native-apple .screen img { object-fit: contain; }
|
|
2675
|
+
@media (orientation: landscape) {
|
|
2676
|
+
body.has-copy .device.native-apple {
|
|
2677
|
+
width: min(60vw, ${84 * imageWidth / imageHeight}vh);
|
|
2678
|
+
height: auto;
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
</style>` : ''}
|
|
2617
2682
|
${createPromotionalDecorations(palette)}
|
|
2618
2683
|
${createScreenshotCopyMarkup(screenshotCopy)}
|
|
2619
|
-
<main class="device ${profile.frameType}${
|
|
2620
|
-
<div class="screen">${phoneStatusBar}<img src="${screenshotDataUrl}" />${phoneNavigation}${
|
|
2684
|
+
<main class="device ${profile.frameType}${useDuoFrame ? ' iphone-duo' : ''}">
|
|
2685
|
+
<div class="screen">${phoneStatusBar}<img src="${screenshotDataUrl}" />${phoneNavigation}${useDuoFrame ? '<div class="duo-camera" aria-hidden="true"></div>' : ''}</div>
|
|
2621
2686
|
${getFrameAdornment(profile.frameType)}
|
|
2622
2687
|
</main>
|
|
2623
2688
|
</body>
|
|
@@ -3836,6 +3901,134 @@ async function runInteractiveCaptureLoop(
|
|
|
3836
3901
|
console.log('\nResponsive screenshot tool closed.');
|
|
3837
3902
|
}
|
|
3838
3903
|
|
|
3904
|
+
function isAppleProfile(profile) {
|
|
3905
|
+
return /^(iphone|ipad|apple)(-|$)/.test(profile.name) || profile.name === 'mac';
|
|
3906
|
+
}
|
|
3907
|
+
|
|
3908
|
+
function validateAppleScreenshot(profile, metadata, fileName) {
|
|
3909
|
+
const family = profile.name === 'iphone-duo' ? 'iphone-duo'
|
|
3910
|
+
: profile.name.startsWith('ipad') ? 'ipad' : 'iphone';
|
|
3911
|
+
const validSize = appleScreenshotSizes[family].some(([width, height]) => (
|
|
3912
|
+
(metadata.width === width && metadata.height === height)
|
|
3913
|
+
|| (metadata.width === height && metadata.height === width)
|
|
3914
|
+
));
|
|
3915
|
+
if (!validSize) {
|
|
3916
|
+
throw new Error(`${fileName}: ${metadata.width}x${metadata.height} is not a supported full-display ${family} screenshot size. Capture the correct Apple device; do not resize another device's image.`);
|
|
3917
|
+
}
|
|
3918
|
+
if (metadata.hasAlpha) {
|
|
3919
|
+
throw new Error(`${fileName}: Apple screenshots cannot include an alpha channel. Export an RGB PNG without transparency.`);
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
|
|
3923
|
+
async function importAppleScreenshots(options) {
|
|
3924
|
+
const profiles = selectProfiles(options.profiles);
|
|
3925
|
+
if (!options.profiles || profiles.length !== 1
|
|
3926
|
+
|| !['iphone', 'iphone-duo', 'ipad', 'iphone-store', 'ipad-store'].includes(profiles[0].name)) {
|
|
3927
|
+
throw new Error('--apple-source requires one profile: iphone, iphone-duo, ipad, iphone-store, or ipad-store.');
|
|
3928
|
+
}
|
|
3929
|
+
if (options.frameExisting || options.reframeExisting || options.dryRun
|
|
3930
|
+
|| options.refreshInfo || options.vivoExisting || options.oppoExisting
|
|
3931
|
+
|| options.amazonAssets || options.samsungAssets || options.prefix || options.screenshots
|
|
3932
|
+
|| options.deleteRequested || options.iosFrame) {
|
|
3933
|
+
throw new Error('Use --apple-source separately from capture, reframe, asset, and screenshot-selection options.');
|
|
3934
|
+
}
|
|
3935
|
+
|
|
3936
|
+
const sourceDirectory = fs.realpathSync(path.resolve(options.appleSource));
|
|
3937
|
+
const isExistingSource = options.appleExisting
|
|
3938
|
+
&& sourceDirectory === fs.realpathSync(path.join(screenshotDirectory, profiles[0].outputDirectory));
|
|
3939
|
+
if (options.appleExisting && !isExistingSource) {
|
|
3940
|
+
throw new Error('--apple-existing only reads the selected Store-image-Screenshot profile folder.');
|
|
3941
|
+
}
|
|
3942
|
+
for (const legacyProfile of isExistingSource ? [] : deviceProfiles.filter(isAppleProfile)) {
|
|
3943
|
+
for (const root of [screenshotDirectory, inFrameDirectory, uploadDirectory]) {
|
|
3944
|
+
const legacyDirectory = path.join(root, legacyProfile.outputDirectory);
|
|
3945
|
+
if (!fs.existsSync(legacyDirectory)) continue;
|
|
3946
|
+
const relative = path.relative(fs.realpathSync(legacyDirectory), sourceDirectory);
|
|
3947
|
+
if (!relative || (relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative))) {
|
|
3948
|
+
throw new Error('This source is a legacy Android replay/framed Apple output. Import original captures from the actual Apple app instead.');
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
const sourceFiles = fs.readdirSync(sourceDirectory, { withFileTypes: true })
|
|
3953
|
+
.filter((entry) => entry.isFile() && /\.png$/i.test(entry.name))
|
|
3954
|
+
.map((entry) => entry.name)
|
|
3955
|
+
.sort((first, second) => first.localeCompare(second, undefined, { numeric: true }));
|
|
3956
|
+
if (!sourceFiles.length) throw new Error('No PNG captures found in --apple-source.');
|
|
3957
|
+
|
|
3958
|
+
const profile = profiles[0];
|
|
3959
|
+
const nativeDirectory = path.join(uploadDirectory, isExistingSource ? 'Apple-Reprocessed' : 'Apple-Native');
|
|
3960
|
+
const originalsDirectory = path.join(nativeDirectory, 'Originals', profile.outputDirectory);
|
|
3961
|
+
const promotionalDirectory = path.join(nativeDirectory, 'Promotional');
|
|
3962
|
+
// Preflight the complete import before writing anything. Never overwrite previous assets.
|
|
3963
|
+
const captures = [];
|
|
3964
|
+
const sizeCounts = new Map();
|
|
3965
|
+
const plannedOutputs = new Set();
|
|
3966
|
+
for (const fileName of sourceFiles) {
|
|
3967
|
+
const buffer = fs.readFileSync(path.join(sourceDirectory, fileName));
|
|
3968
|
+
const metadata = await sharp(buffer).metadata();
|
|
3969
|
+
if (metadata.format !== 'png' || !metadata.width || !metadata.height
|
|
3970
|
+
|| (metadata.pages || 1) !== 1 || (metadata.orientation && metadata.orientation !== 1)) {
|
|
3971
|
+
throw new Error(`Use an unrotated, single-image PNG capture: ${fileName}`);
|
|
3972
|
+
}
|
|
3973
|
+
validateAppleScreenshot(profile, metadata, fileName);
|
|
3974
|
+
// Decode the whole image before copying it; a valid PNG header alone is insufficient.
|
|
3975
|
+
await sharp(buffer).stats();
|
|
3976
|
+
const size = `${metadata.width}x${metadata.height}`;
|
|
3977
|
+
if (!sizeCounts.has(size)) {
|
|
3978
|
+
const existingDirectory = path.join(originalsDirectory, size);
|
|
3979
|
+
const existingCount = fs.existsSync(existingDirectory)
|
|
3980
|
+
? fs.readdirSync(existingDirectory).filter((name) => /\.png$/i.test(name)).length : 0;
|
|
3981
|
+
sizeCounts.set(size, existingCount);
|
|
3982
|
+
}
|
|
3983
|
+
sizeCounts.set(size, sizeCounts.get(size) + 1);
|
|
3984
|
+
if (sizeCounts.get(size) > 10) throw new Error(`Apple allows up to 10 screenshots per size; ${size} exceeds that limit.`);
|
|
3985
|
+
const originalPath = path.join(originalsDirectory, size, fileName);
|
|
3986
|
+
const promotionalPath = path.join(promotionalDirectory, profile.outputDirectory, size, `${path.parse(fileName).name}.png`);
|
|
3987
|
+
for (const target of [originalPath, ...(options.applePromotional ? [promotionalPath] : [])]) {
|
|
3988
|
+
if (fs.existsSync(target)) throw new Error(`Output already exists; move it or rename the input first: ${target}`);
|
|
3989
|
+
const key = target.toLowerCase();
|
|
3990
|
+
if (plannedOutputs.has(key)) throw new Error(`Duplicate output filename: ${fileName}`);
|
|
3991
|
+
plannedOutputs.add(key);
|
|
3992
|
+
}
|
|
3993
|
+
if (options.applePromotional && !getScreenshotCopy(fileName)) {
|
|
3994
|
+
throw new Error(`No promotional copy for ${fileName}. Use a matching screenshot-copy.json name or omit --apple-promotional.`);
|
|
3995
|
+
}
|
|
3996
|
+
captures.push({ fileName, buffer, metadata, originalPath, size });
|
|
3997
|
+
}
|
|
3998
|
+
|
|
3999
|
+
let browser;
|
|
4000
|
+
let client;
|
|
4001
|
+
try {
|
|
4002
|
+
if (options.applePromotional) {
|
|
4003
|
+
browser = await launchReplayBrowser();
|
|
4004
|
+
client = await createReplayPage(browser);
|
|
4005
|
+
}
|
|
4006
|
+
for (const { fileName, buffer, metadata, originalPath, size } of captures) {
|
|
4007
|
+
fs.mkdirSync(path.dirname(originalPath), { recursive: true });
|
|
4008
|
+
fs.writeFileSync(originalPath, buffer, { flag: 'wx' });
|
|
4009
|
+
console.log(`Saved unchanged source image (${metadata.width}x${metadata.height}): ${originalPath}`);
|
|
4010
|
+
if (client) {
|
|
4011
|
+
await captureFramedScreenshot(client, buffer, {
|
|
4012
|
+
...profile,
|
|
4013
|
+
outputDirectory: path.join(profile.outputDirectory, size),
|
|
4014
|
+
frameType: 'native-apple',
|
|
4015
|
+
screenshotOnly: false,
|
|
4016
|
+
framedWidth: metadata.width,
|
|
4017
|
+
framedHeight: metadata.height,
|
|
4018
|
+
}, fileName, true, promotionalDirectory);
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
} finally {
|
|
4022
|
+
if (client) client.close();
|
|
4023
|
+
if (browser) await closeReplayBrowser(browser);
|
|
4024
|
+
}
|
|
4025
|
+
console.log('Apple image processing complete. Source dimensions are preserved; no system bars or device frames were added.');
|
|
4026
|
+
console.log(isExistingSource
|
|
4027
|
+
? 'These images reuse Android-derived app content; they are not native iOS/iPadOS captures.'
|
|
4028
|
+
: 'Use captures from the actual iOS/iPadOS app; this tool cannot verify their origin.');
|
|
4029
|
+
console.log('Check current App Store Connect size requirements and replace old screenshots in every size/localization.');
|
|
4030
|
+
}
|
|
4031
|
+
|
|
3839
4032
|
async function getInteractiveOptions(options) {
|
|
3840
4033
|
if (
|
|
3841
4034
|
!process.stdin.isTTY
|
|
@@ -3882,6 +4075,34 @@ async function main() {
|
|
|
3882
4075
|
return;
|
|
3883
4076
|
}
|
|
3884
4077
|
|
|
4078
|
+
if (options.appleExisting) {
|
|
4079
|
+
if (options.appleSource) throw new Error('Use --apple-existing separately from --apple-source.');
|
|
4080
|
+
const profiles = selectProfiles(options.profiles || 'iphone,iphone-duo,ipad');
|
|
4081
|
+
if (profiles.some((profile) => !['iphone', 'iphone-duo', 'ipad', 'iphone-store', 'ipad-store'].includes(profile.name))) {
|
|
4082
|
+
throw new Error('--apple-existing supports only iPhone/iPad screenshot profiles.');
|
|
4083
|
+
}
|
|
4084
|
+
for (const profile of profiles) {
|
|
4085
|
+
await importAppleScreenshots({
|
|
4086
|
+
...options,
|
|
4087
|
+
profiles: profile.name,
|
|
4088
|
+
appleSource: path.join(screenshotDirectory, profile.outputDirectory),
|
|
4089
|
+
applePromotional: true,
|
|
4090
|
+
});
|
|
4091
|
+
}
|
|
4092
|
+
return;
|
|
4093
|
+
}
|
|
4094
|
+
|
|
4095
|
+
if (options.appleSource) {
|
|
4096
|
+
await importAppleScreenshots(options);
|
|
4097
|
+
return;
|
|
4098
|
+
}
|
|
4099
|
+
if (options.applePromotional) {
|
|
4100
|
+
throw new Error('--apple-promotional requires --apple-source.');
|
|
4101
|
+
}
|
|
4102
|
+
if (options.iosFrame) {
|
|
4103
|
+
throw new Error('--ios-frame is no longer used. Import native Apple captures with --apple-source.');
|
|
4104
|
+
}
|
|
4105
|
+
|
|
3885
4106
|
if (options.deleteRequested) {
|
|
3886
4107
|
deleteMatchingImages(options.deleteNames);
|
|
3887
4108
|
return;
|
|
@@ -3889,6 +4110,21 @@ async function main() {
|
|
|
3889
4110
|
|
|
3890
4111
|
options = await getInteractiveOptions(options);
|
|
3891
4112
|
|
|
4113
|
+
if (!options.amazonAssets && !options.samsungAssets && !options.vivoExisting
|
|
4114
|
+
&& !options.oppoExisting && !options.refreshInfo) {
|
|
4115
|
+
const selected = selectProfiles(options.profiles);
|
|
4116
|
+
const appleProfiles = selected.filter(isAppleProfile);
|
|
4117
|
+
if (appleProfiles.length) {
|
|
4118
|
+
const explicitSelection = options.profiles.split(',').some((name) => name.trim() && name.trim().toLowerCase() !== 'all');
|
|
4119
|
+
if (explicitSelection) {
|
|
4120
|
+
throw new Error('Apple screenshots require native captures. Import iPhone/iPad captures with --apple-source <directory> --profiles iphone, iphone-duo, or ipad (one per import). Android replay/reframing is disabled for every Apple profile, including Watch, Vision Pro, TV, and Mac.');
|
|
4121
|
+
}
|
|
4122
|
+
options.profiles = selected.filter((profile) => !isAppleProfile(profile))
|
|
4123
|
+
.map((profile) => profile.name).join(',');
|
|
4124
|
+
console.log('Skipping all Apple Android replay profiles. Import native iPhone/iPad captures with --apple-source.');
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
|
|
3892
4128
|
if (!options.dryRun) {
|
|
3893
4129
|
initializeScreenshotData();
|
|
3894
4130
|
}
|
|
@@ -4022,7 +4258,7 @@ async function main() {
|
|
|
4022
4258
|
replayClient = await createReplayPage(replayBrowser);
|
|
4023
4259
|
|
|
4024
4260
|
console.log(
|
|
4025
|
-
'Apple
|
|
4261
|
+
'Non-Apple profiles are viewport approximations of the live Android WebView. Apple profiles require native captures.',
|
|
4026
4262
|
);
|
|
4027
4263
|
|
|
4028
4264
|
if (interactiveMode) {
|