codeplay-common 4.10.7 → 4.10.9

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.
@@ -38,7 +38,9 @@ function updateBuildScripts(scripts) {
38
38
  next['ionic:build'] = next['ionic:build'] || 'npm run build';
39
39
  next['ionic:serve'] = next['ionic:serve'] || 'cross-env NODE_ENV=development vite';
40
40
  next['baseline-profile:setup'] = next['baseline-profile:setup'] || 'node scripts-common/setup-baseline-profile.js';
41
- next['baseline-profile:generate'] = next['baseline-profile:generate'] || 'cd android && gradlew.bat :app:generateBaselineProfile --console=plain';
41
+ if (!next['baseline-profile:generate'] || next['baseline-profile:generate'] === 'cd android && gradlew.bat :app:generateBaselineProfile --console=plain') {
42
+ next['baseline-profile:generate'] = 'node scripts-common/generate-baseline-profile.js';
43
+ }
42
44
  next.release = next.release || 'node finalrelease';
43
45
  return next;
44
46
  }
@@ -0,0 +1,42 @@
1
+ const { execFileSync, spawnSync } = require('child_process');
2
+ const path = require('path');
3
+
4
+ function main() {
5
+ const devices = execFileSync('adb', ['devices'], { encoding: 'utf8' })
6
+ .split(/\r?\n/)
7
+ .map(line => line.match(/^(.+?)\tdevice\s*$/))
8
+ .filter(Boolean)
9
+ .map(match => match[1]);
10
+ const requestedSerial = process.env.ANDROID_SERIAL?.trim();
11
+ if (requestedSerial?.includes(',')) {
12
+ throw new Error('Set ANDROID_SERIAL to one device connection for Baseline Profile generation.');
13
+ }
14
+ const serial = requestedSerial || devices[0];
15
+ if (!serial || !devices.includes(serial)) {
16
+ throw new Error(requestedSerial
17
+ ? `ADB device is not online: ${requestedSerial}`
18
+ : 'No online ADB device. Connect an Android device before generating a Baseline Profile.');
19
+ }
20
+
21
+ console.log(`Generating Baseline Profile using one ADB connection: ${serial}`);
22
+ const args = [':app:generateBaselineProfile', '--console=plain'];
23
+ const result = spawnSync(
24
+ process.platform === 'win32' ? 'cmd.exe' : './gradlew',
25
+ process.platform === 'win32' ? ['/d', '/s', '/c', `gradlew.bat ${args.join(' ')}`] : args,
26
+ {
27
+ cwd: path.resolve(__dirname, '..', 'android'),
28
+ env: { ...process.env, ANDROID_SERIAL: serial },
29
+ stdio: 'inherit',
30
+ }
31
+ );
32
+ if (result.error) throw result.error;
33
+ if (result.signal) throw new Error(`Baseline Profile generation stopped by ${result.signal}.`);
34
+ process.exitCode = result.status ?? 1;
35
+ }
36
+
37
+ try {
38
+ main();
39
+ } catch (error) {
40
+ console.error(`Baseline Profile generation failed: ${error.message}`);
41
+ process.exitCode = 1;
42
+ }
@@ -3,7 +3,8 @@
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
- //App Store: import actual iOS/iPadOS PNG captures; Android replay is not an Apple screenshot source.
6
+ //iPhone, iPhone Duo and iPad use plain screenshots without device frames or synthetic system bars.
7
+ //Reframing recovers missing iPhone/iPad images from existing captures without cropping.
7
8
  //node take-screen-image.js --apple-source "C:\captures\iphone" --profiles iphone
8
9
  //Add --apple-promotional to reuse screenshot-copy.json captions around the original capture.
9
10
 
@@ -179,7 +180,7 @@ const deviceProfiles = [
179
180
  name: 'iphone',
180
181
  label: 'iPhone Dynamic Island medium-display App Store viewport',
181
182
  outputDirectory: 'iPhone',
182
- frameType: 'iphone',
183
+ frameType: 'native-apple',
183
184
  width: 402,
184
185
  height: 874,
185
186
  deviceScaleFactor: 3,
@@ -192,7 +193,7 @@ const deviceProfiles = [
192
193
  name: 'iphone-duo',
193
194
  label: 'iPhone Duo inner-display App Store viewport',
194
195
  outputDirectory: 'iPhone-Duo',
195
- frameType: 'iphone',
196
+ frameType: 'native-apple',
196
197
  showDynamicIsland: false,
197
198
  width: 669,
198
199
  height: 951,
@@ -206,7 +207,7 @@ const deviceProfiles = [
206
207
  name: 'ipad',
207
208
  label: 'iPad 13-inch App Store viewport',
208
209
  outputDirectory: 'iPad',
209
- frameType: 'ipad',
210
+ frameType: 'native-apple',
210
211
  width: 1032,
211
212
  height: 1376,
212
213
  deviceScaleFactor: 2,
@@ -219,7 +220,7 @@ const deviceProfiles = [
219
220
  name: 'ipad-store',
220
221
  label: 'iPad App Store screenshot set',
221
222
  outputDirectory: 'iPad-Store',
222
- frameType: 'tablet',
223
+ frameType: 'native-apple',
223
224
  width: 1024,
224
225
  height: 1366,
225
226
  deviceScaleFactor: 2,
@@ -230,7 +231,7 @@ const deviceProfiles = [
230
231
  name: 'iphone-store',
231
232
  label: 'iPhone Dynamic Island medium-display App Store screenshot set',
232
233
  outputDirectory: 'iPhone-Store',
233
- frameType: 'iphone',
234
+ frameType: 'native-apple',
234
235
  width: 402,
235
236
  height: 874,
236
237
  deviceScaleFactor: 3,
@@ -617,7 +618,7 @@ function printProfiles() {
617
618
  + `(folder ${profile.outputDirectory}, viewport ${profile.width}x${profile.height}, `
618
619
  + `${(profile.outputFormat || 'png').toUpperCase()} ${outputWidth}x${outputHeight}${framedSize})`,
619
620
  );
620
- if (isAppleProfile(profile)) {
621
+ if (isAppleProfile(profile) && profile.frameType !== 'native-apple') {
621
622
  console.log(' Native Apple capture required; Android replay/reframing is disabled.');
622
623
  }
623
624
  if (profile.storeRequirements) {
@@ -655,7 +656,9 @@ function printHelp() {
655
656
  console.log('');
656
657
  console.log('A normal capture automatically creates any missing Amazon, Samsung, and Play Store assets.');
657
658
  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('iPhone/iPad profiles capture and reframe plain screenshots without device frames or synthetic system bars.');
660
+ console.log('Reframing recovers missing iPhone/iPad screenshots from existing captures, preserving content with padding.');
661
+ console.log('Use --apple-source with one profile to import original iOS/iPadOS captures instead.');
659
662
  console.log('Name captures 1.png, 2.png, etc. to reuse existing promotional copy.');
660
663
  console.log('Apple originals and optional promotions are saved separately under Store-upload/Apple-Native.');
661
664
  console.log('Imports preserve source dimensions and system bars; the tool cannot verify capture provenance.');
@@ -693,7 +696,7 @@ async function chooseFrameStyles(options) {
693
696
  if (terminal) terminal.close();
694
697
  }
695
698
 
696
- console.log(`Frame selection: Android ${choices.android}. Apple captures keep their native appearance.`);
699
+ console.log(`Frame selection: Android ${choices.android}. iPhone/iPad screenshots have no device frame.`);
697
700
  return choices;
698
701
  }
699
702
 
@@ -3407,8 +3410,6 @@ async function captureFramedScreenshot(
3407
3410
  skipExisting = false,
3408
3411
  outputRootDirectory = inFrameDirectory,
3409
3412
  ) {
3410
- if (profile.screenshotOnly) return false;
3411
-
3412
3413
  const outputExtension = profile.outputExtension || path.extname(screenshotFileName).slice(1) || 'png';
3413
3414
  const outputFileName = `${path.parse(screenshotFileName).name}.${outputExtension}`;
3414
3415
  const framedProfileDirectory = path.join(outputRootDirectory, profile.outputDirectory);
@@ -3419,6 +3420,14 @@ async function captureFramedScreenshot(
3419
3420
  return false;
3420
3421
  }
3421
3422
 
3423
+ if (profile.screenshotOnly) {
3424
+ if (profile.frameType !== 'native-apple') return false;
3425
+ fs.mkdirSync(framedProfileDirectory, { recursive: true });
3426
+ fs.writeFileSync(framedScreenshotPath, screenshotBuffer, { flag: skipExisting ? 'wx' : 'w' });
3427
+ console.log(`Saved plain ${profile.label}: ${framedScreenshotPath}`);
3428
+ return true;
3429
+ }
3430
+
3422
3431
  const originalImageMetadata = await sharp(screenshotBuffer).metadata();
3423
3432
  const hasCustomFrameSize = Boolean(profile.framedWidth && profile.framedHeight);
3424
3433
  const frameWidth = profile.framedWidth || profile.width;
@@ -3570,8 +3579,8 @@ async function captureProfile(client, snapshot, profile, screenshotFileName) {
3570
3579
  frameId: frameTree.frameTree.frame.id,
3571
3580
  html: snapshot.html,
3572
3581
  });
3573
- if (profile.name === 'iphone-duo') {
3574
- // The Duo frame supplies its own system bars; do not capture Android insets again.
3582
+ if (profile.frameType === 'native-apple') {
3583
+ // Plain iPhone/iPad captures do not include Android system-bar spacing.
3575
3584
  await client.send('Runtime.evaluate', {
3576
3585
  expression: `(() => {
3577
3586
  const style = document.createElement('style');
@@ -3659,6 +3668,10 @@ async function frameExistingScreenshots(
3659
3668
  );
3660
3669
  const foundScreenshotNames = new Set();
3661
3670
 
3671
+ for (const profile of selectedProfiles.filter((item) => item.frameType === 'native-apple')) {
3672
+ await recoverMissingAppleScreenshots(profile, selectedScreenshotNames);
3673
+ }
3674
+
3662
3675
  try {
3663
3676
  replayBrowser = await launchReplayBrowser();
3664
3677
  replayClient = await createReplayPage(replayBrowser);
@@ -3901,6 +3914,40 @@ async function runInteractiveCaptureLoop(
3901
3914
  console.log('\nResponsive screenshot tool closed.');
3902
3915
  }
3903
3916
 
3917
+ async function recoverMissingAppleScreenshots(profile, selectedNames) {
3918
+ const sourceFolders = profile.name.startsWith('ipad')
3919
+ ? ['Amazon-Tab', 'Android-Tab-10inch', 'Android-Tab-7inch', 'Android']
3920
+ : ['Android', 'Amazon-Phone'];
3921
+ const sources = new Map();
3922
+ for (const folder of sourceFolders) {
3923
+ const directory = path.join(screenshotDirectory, folder);
3924
+ if (!fs.existsSync(directory)) continue;
3925
+ for (const fileName of fs.readdirSync(directory).sort()) {
3926
+ if (!/\.png$/i.test(fileName)) continue;
3927
+ const name = path.parse(fileName).name;
3928
+ if (selectedNames.size && !selectedNames.has(name)) continue;
3929
+ if (!sources.has(name)) sources.set(name, path.join(directory, fileName));
3930
+ }
3931
+ }
3932
+ for (const [name, sourcePath] of sources) {
3933
+ const outputPath = path.join(screenshotDirectory, profile.outputDirectory, `${name}.png`);
3934
+ if (fs.existsSync(outputPath)) continue;
3935
+ const image = sharp(sourcePath);
3936
+ const { width, height } = await image.metadata();
3937
+ const { data } = await image.clone().resize(1, 1, { fit: 'fill' }).toColourspace('srgb').removeAlpha().raw().toBuffer({ resolveWithObject: true });
3938
+ const landscape = width > height;
3939
+ const targetWidth = Math.min(profile.width, profile.height) * profile.deviceScaleFactor;
3940
+ const targetHeight = Math.max(profile.width, profile.height) * profile.deviceScaleFactor;
3941
+ const buffer = await image.resize(landscape ? targetHeight : targetWidth, landscape ? targetWidth : targetHeight, {
3942
+ fit: 'contain',
3943
+ background: { r: data[0], g: data[1], b: data[2], alpha: 1 },
3944
+ }).removeAlpha().png().toBuffer();
3945
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
3946
+ fs.writeFileSync(outputPath, buffer, { flag: 'wx' });
3947
+ console.log(`Recovered plain screenshot from ${sourcePath}: ${outputPath}`);
3948
+ }
3949
+ }
3950
+
3904
3951
  function isAppleProfile(profile) {
3905
3952
  return /^(iphone|ipad|apple)(-|$)/.test(profile.name) || profile.name === 'mac';
3906
3953
  }
@@ -4113,15 +4160,16 @@ async function main() {
4113
4160
  if (!options.amazonAssets && !options.samsungAssets && !options.vivoExisting
4114
4161
  && !options.oppoExisting && !options.refreshInfo) {
4115
4162
  const selected = selectProfiles(options.profiles);
4116
- const appleProfiles = selected.filter(isAppleProfile);
4163
+ const requiresNativeCapture = (profile) => isAppleProfile(profile) && profile.frameType !== 'native-apple';
4164
+ const appleProfiles = selected.filter(requiresNativeCapture);
4117
4165
  if (appleProfiles.length) {
4118
4166
  const explicitSelection = options.profiles.split(',').some((name) => name.trim() && name.trim().toLowerCase() !== 'all');
4119
4167
  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.');
4168
+ throw new Error('Watch, Vision Pro, TV and Mac profiles require native captures. iPhone, iPhone Duo and iPad support plain screenshot capture and reframing.');
4121
4169
  }
4122
- options.profiles = selected.filter((profile) => !isAppleProfile(profile))
4170
+ options.profiles = selected.filter((profile) => !requiresNativeCapture(profile))
4123
4171
  .map((profile) => profile.name).join(',');
4124
- console.log('Skipping all Apple Android replay profiles. Import native iPhone/iPad captures with --apple-source.');
4172
+ console.log('Skipping Watch, Vision Pro, TV and Mac profiles. iPhone/iPad plain screenshots are enabled.');
4125
4173
  }
4126
4174
  }
4127
4175
 
@@ -4258,7 +4306,7 @@ async function main() {
4258
4306
  replayClient = await createReplayPage(replayBrowser);
4259
4307
 
4260
4308
  console.log(
4261
- 'Non-Apple profiles are viewport approximations of the live Android WebView. Apple profiles require native captures.',
4309
+ 'Profiles are viewport approximations of the live Android WebView. iPhone/iPad outputs use plain screenshots without device frames.',
4262
4310
  );
4263
4311
 
4264
4312
  if (interactiveMode) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeplay-common",
3
- "version": "4.10.7",
3
+ "version": "4.10.9",
4
4
  "description": "Common build scripts and files",
5
5
  "scripts": {
6
6
  "postinstall": "node scripts/sync-files.js",