codeplay-common 4.6.7 → 4.6.8

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.
@@ -1,12 +1,15 @@
1
1
  //node take-screen-image.js --delete 3
2
2
  //node take-screen-image.js --delete 3,4,5
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
+ //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
+ //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.
4
6
 
5
7
 
6
8
  const fs = require('node:fs');
7
9
  const path = require('node:path');
8
10
  const readline = require('node:readline/promises');
9
11
  const { spawn, spawnSync } = require('node:child_process');
12
+ const sharp = require('sharp');
10
13
 
11
14
  const screenshotDirectory = path.join(__dirname, 'Auto-Screenshot', 'Store-image-Screenshot');
12
15
  const inFrameDirectory = path.join(__dirname, 'Auto-Screenshot', 'Store-image-Frame');
@@ -53,6 +56,52 @@ const deviceProfiles = [
53
56
  orientationSensitive: true,
54
57
  mobile: true,
55
58
  },
59
+ {
60
+ name: 'vivo-store',
61
+ label: 'Vivo Store portrait screenshot',
62
+ outputDirectory: 'Vivo-Store',
63
+ frameType: 'android-phone',
64
+ width: 360,
65
+ height: 720,
66
+ deviceScaleFactor: 3,
67
+ framedWidth: 1080,
68
+ framedHeight: 2160,
69
+ orientationSensitive: false,
70
+ mobile: true,
71
+ storeRequirements: {
72
+ maxFileSizeBytes: 5 * 1024 * 1024,
73
+ minPixels: 320,
74
+ maxPixels: 3840,
75
+ minAspectRatio: 0.5,
76
+ maxAspectRatio: 2,
77
+ minImages: 4,
78
+ maxImages: 8,
79
+ },
80
+ },
81
+ {
82
+ name: 'oppo-store',
83
+ label: 'Oppo Store portrait screenshot',
84
+ outputDirectory: 'Oppo-Store',
85
+ frameType: 'android-phone',
86
+ width: 360,
87
+ height: 640,
88
+ deviceScaleFactor: 3,
89
+ framedWidth: 1080,
90
+ framedHeight: 1920,
91
+ orientationSensitive: false,
92
+ mobile: true,
93
+ outputFormat: 'jpeg',
94
+ outputExtension: 'jpg',
95
+ storeRequirements: {
96
+ maxFileSizeBytes: 3 * 1024 * 1024,
97
+ minPixels: 320,
98
+ maxPixels: 1920,
99
+ minAspectRatio: 0.5,
100
+ maxAspectRatio: 0.6,
101
+ minImages: 3,
102
+ maxImages: 5,
103
+ },
104
+ },
56
105
  {
57
106
  name: 'android-tablet-7-inch',
58
107
  aliases: ['android-tablet'],
@@ -353,31 +402,33 @@ function getSelectedOutputDirectories(selectedProfiles) {
353
402
  }
354
403
 
355
404
  function getNextNumberedFileName(selectedProfiles) {
405
+ const extension = selectedProfiles[0]?.outputExtension || 'png';
356
406
  const highestNumber = getSelectedOutputDirectories(selectedProfiles)
357
407
  .filter((directory) => fs.existsSync(directory))
358
408
  .reduce((highestAcrossDirectories, directory) => {
359
409
  const highestInDirectory = fs.readdirSync(directory, { withFileTypes: true })
360
410
  .filter((entry) => entry.isFile())
361
411
  .reduce((highest, entry) => {
362
- const match = entry.name.match(/^(\d+)\.png$/i);
412
+ const match = entry.name.match(new RegExp(`^(\\d+)\\.${extension}$`, 'i'));
363
413
  return match ? Math.max(highest, Number(match[1])) : highest;
364
414
  }, 0);
365
415
 
366
416
  return Math.max(highestAcrossDirectories, highestInDirectory);
367
417
  }, 0);
368
418
 
369
- return `${highestNumber + 1}.png`;
419
+ return `${highestNumber + 1}.${extension}`;
370
420
  }
371
421
 
372
422
  function getUniqueFileName(prefix, selectedProfiles) {
423
+ const extension = selectedProfiles[0]?.outputExtension || 'png';
373
424
  const outputDirectories = getSelectedOutputDirectories(selectedProfiles);
374
- let fileName = `${prefix}.png`;
425
+ let fileName = `${prefix}.${extension}`;
375
426
  let duplicateNumber = 2;
376
427
 
377
428
  while (outputDirectories.some((directory) => (
378
429
  fs.existsSync(path.join(directory, fileName))
379
430
  ))) {
380
- fileName = `${prefix}-${duplicateNumber}.png`;
431
+ fileName = `${prefix}-${duplicateNumber}.${extension}`;
381
432
  duplicateNumber += 1;
382
433
  }
383
434
 
@@ -385,7 +436,7 @@ function getUniqueFileName(prefix, selectedProfiles) {
385
436
  }
386
437
 
387
438
  function getScreenshotFileName(fileNameInput, selectedProfiles) {
388
- const inputWithoutExtension = fileNameInput.trim().replace(/\.png$/i, '');
439
+ const inputWithoutExtension = fileNameInput.trim().replace(/\.(png|jpe?g)$/i, '');
389
440
 
390
441
  if (!inputWithoutExtension) {
391
442
  return getNextNumberedFileName(selectedProfiles);
@@ -411,6 +462,8 @@ function parseArguments() {
411
462
  samsungAssets: false,
412
463
  dryRun: false,
413
464
  frameExisting: false,
465
+ vivoExisting: false,
466
+ oppoExisting: false,
414
467
  help: false,
415
468
  list: false,
416
469
  deleteRequested: false,
@@ -430,6 +483,10 @@ function parseArguments() {
430
483
  options.dryRun = true;
431
484
  } else if (argument === '--frame-existing') {
432
485
  options.frameExisting = true;
486
+ } else if (argument === '--vivo-existing') {
487
+ options.vivoExisting = true;
488
+ } else if (argument === '--oppo-existing') {
489
+ options.oppoExisting = true;
433
490
  } else if (argument === '--help' || argument === '-h') {
434
491
  options.help = true;
435
492
  } else if (argument === '--list') {
@@ -460,13 +517,20 @@ function printProfiles() {
460
517
  const outputWidth = profile.width * profile.deviceScaleFactor;
461
518
  const outputHeight = profile.height * profile.deviceScaleFactor;
462
519
  const framedSize = profile.framedWidth
463
- ? `, framed PNG ${profile.framedWidth}x${profile.framedHeight}`
520
+ ? `, framed ${(profile.outputFormat || 'png').toUpperCase()} ${profile.framedWidth}x${profile.framedHeight}`
464
521
  : '';
465
522
  console.log(
466
523
  `${profile.name.padEnd(15)} ${profile.label} `
467
524
  + `(folder ${profile.outputDirectory}, viewport ${profile.width}x${profile.height}, `
468
- + `PNG ${outputWidth}x${outputHeight}${framedSize})`,
525
+ + `${(profile.outputFormat || 'png').toUpperCase()} ${outputWidth}x${outputHeight}${framedSize})`,
469
526
  );
527
+ if (profile.storeRequirements) {
528
+ console.log(
529
+ ` Store limits: ${profile.storeRequirements.minImages}-${profile.storeRequirements.maxImages} images, `
530
+ + `max ${profile.storeRequirements.maxFileSizeBytes / (1024 * 1024)} MB, `
531
+ + `width/height ${profile.storeRequirements.minAspectRatio}-${profile.storeRequirements.maxAspectRatio}:1`,
532
+ );
533
+ }
470
534
  }
471
535
  }
472
536
 
@@ -478,6 +542,8 @@ function printHelp() {
478
542
  console.log(' node take-screen-image.js --profiles iphone,ipad');
479
543
  console.log(' node take-screen-image.js --prefix home --profiles android-phone,ipad');
480
544
  console.log(' node take-screen-image.js --frame-existing');
545
+ console.log(' node take-screen-image.js --vivo-existing');
546
+ console.log(' node take-screen-image.js --oppo-existing');
481
547
  console.log(' node take-screen-image.js --amazon-assets');
482
548
  console.log(' node take-screen-image.js --samsung-assets');
483
549
  console.log(' node take-screen-image.js --dry-run');
@@ -1154,9 +1220,84 @@ function getFrameAdornment(frameType) {
1154
1220
  return '<div class="xr-status"></div>';
1155
1221
  }
1156
1222
 
1157
- function createDeviceFrameHtml(profile, screenshotBuffer) {
1158
- const screenshotDataUrl = `data:image/png;base64,${screenshotBuffer.toString('base64')}`;
1159
- const aspectRatio = `${profile.width} / ${profile.height}`;
1223
+ function createStoreDeviceFrameHtml(profile, screenshotBuffer, imageWidth, imageHeight, canvasWidth, canvasHeight, imageFormat) {
1224
+ const screenshotDataUrl = `data:image/${imageFormat === 'jpeg' ? 'jpeg' : 'png'};base64,${screenshotBuffer.toString('base64')}`;
1225
+ const screenWidth = Math.floor(Math.min(canvasWidth - 160, (canvasHeight - 180) * imageWidth / imageHeight));
1226
+ const accent = profile.name === 'oppo-store' ? '#76e4c2' : '#9daaff';
1227
+
1228
+ return `<!doctype html>
1229
+ <html>
1230
+ <head>
1231
+ <meta charset="utf-8" />
1232
+ <style>
1233
+ * { box-sizing: border-box; }
1234
+ html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; }
1235
+ body {
1236
+ display: grid;
1237
+ place-items: center;
1238
+ background: radial-gradient(ellipse at 50% 43%, #24344d 0%, #101b2d 47%, #070e1a 100%);
1239
+ }
1240
+ body::before {
1241
+ content: '';
1242
+ position: absolute;
1243
+ width: 82%;
1244
+ height: 62%;
1245
+ border-radius: 50%;
1246
+ background: ${accent};
1247
+ opacity: 0.11;
1248
+ filter: blur(90px);
1249
+ }
1250
+ .device {
1251
+ position: relative;
1252
+ z-index: 1;
1253
+ width: ${screenWidth + 48}px;
1254
+ padding: 34px 18px 20px;
1255
+ border: 6px solid #8d99ac;
1256
+ border-radius: 48px;
1257
+ background: linear-gradient(135deg, #404c60, #111925 35%, #070c15 72%, #59657a);
1258
+ box-shadow: 0 38px 85px rgba(0, 0, 0, 0.58), 0 0 0 2px rgba(255, 255, 255, 0.19) inset;
1259
+ }
1260
+ .screen {
1261
+ width: ${screenWidth}px;
1262
+ background: #fff;
1263
+ box-shadow: 0 0 0 2px #03060c;
1264
+ }
1265
+ .screen img { display: block; width: 100%; height: auto; }
1266
+ .camera {
1267
+ position: absolute;
1268
+ top: 11px;
1269
+ left: 50%;
1270
+ width: 12px;
1271
+ height: 12px;
1272
+ transform: translateX(-50%);
1273
+ border-radius: 50%;
1274
+ background: radial-gradient(circle at 32% 30%, #547496, #070b12 48%, #000 80%);
1275
+ box-shadow: 0 0 0 3px #1b2534;
1276
+ }
1277
+ .side-button {
1278
+ position: absolute;
1279
+ top: 19%;
1280
+ right: -10px;
1281
+ width: 4px;
1282
+ height: 66px;
1283
+ border-radius: 0 3px 3px 0;
1284
+ background: #79869a;
1285
+ }
1286
+ </style>
1287
+ </head>
1288
+ <body>
1289
+ <main class="device">
1290
+ <div class="camera"></div>
1291
+ <div class="screen"><img src="${screenshotDataUrl}" /></div>
1292
+ <div class="side-button"></div>
1293
+ </main>
1294
+ </body>
1295
+ </html>`;
1296
+ }
1297
+
1298
+ function createDeviceFrameHtml(profile, screenshotBuffer, imageWidth = profile.width, imageHeight = profile.height, imageFormat = 'png') {
1299
+ const screenshotDataUrl = `data:image/${imageFormat === 'jpeg' ? 'jpeg' : 'png'};base64,${screenshotBuffer.toString('base64')}`;
1300
+ const aspectRatio = `${imageWidth} / ${imageHeight}`;
1160
1301
 
1161
1302
  return `<!doctype html>
1162
1303
  <html>
@@ -1200,20 +1341,21 @@ function createDeviceFrameHtml(profile, screenshotBuffer) {
1200
1341
  .screen {
1201
1342
  position: relative;
1202
1343
  width: 100%;
1203
- height: 100%;
1344
+ height: 87%;
1204
1345
  overflow: hidden;
1205
- background: #05070b;
1346
+ background: #f5f7fc;
1206
1347
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.95) inset;
1207
1348
  }
1208
1349
  .screen img {
1209
1350
  display: block;
1210
1351
  width: 100%;
1211
1352
  height: 100%;
1212
- object-fit: cover;
1353
+ object-fit: contain;
1354
+ background: #f5f7fc;
1213
1355
  }
1214
1356
  .device.android-phone,
1215
1357
  .device.iphone {
1216
- height: 87%;
1358
+ height: 100%;
1217
1359
  padding: 1.25%;
1218
1360
  border-radius: clamp(24px, 8vw, 70px);
1219
1361
  }
@@ -2040,9 +2182,15 @@ async function captureFramedScreenshot(
2040
2182
  return false;
2041
2183
  }
2042
2184
 
2043
- const hasCustomFrameSize = Boolean(profile.framedWidth && profile.framedHeight);
2044
- const frameWidth = profile.framedWidth || profile.width;
2045
- const frameHeight = profile.framedHeight || profile.height;
2185
+ const originalImageMetadata = profile.storeRequirements
2186
+ ? await sharp(screenshotBuffer).metadata()
2187
+ : null;
2188
+ const frameDimensions = originalImageMetadata?.width && originalImageMetadata?.height
2189
+ ? { width: originalImageMetadata.width, height: originalImageMetadata.height }
2190
+ : null;
2191
+ const hasCustomFrameSize = Boolean(frameDimensions || (profile.framedWidth && profile.framedHeight));
2192
+ const frameWidth = frameDimensions?.width || profile.framedWidth || profile.width;
2193
+ const frameHeight = frameDimensions?.height || profile.framedHeight || profile.height;
2046
2194
  const frameScaleFactor = hasCustomFrameSize ? 1 : profile.deviceScaleFactor;
2047
2195
 
2048
2196
  await client.send('Emulation.setDeviceMetricsOverride', {
@@ -2059,7 +2207,12 @@ async function captureFramedScreenshot(
2059
2207
  const frameTree = await client.send('Page.getFrameTree');
2060
2208
  await client.send('Page.setDocumentContent', {
2061
2209
  frameId: frameTree.frameTree.frame.id,
2062
- html: createDeviceFrameHtml(profile, screenshotBuffer),
2210
+ html: profile.name === 'vivo-store' || profile.name === 'oppo-store'
2211
+ ? createStoreDeviceFrameHtml(
2212
+ profile, screenshotBuffer, originalImageMetadata.width, originalImageMetadata.height,
2213
+ frameWidth, frameHeight, originalImageMetadata.format,
2214
+ )
2215
+ : createDeviceFrameHtml(profile, screenshotBuffer, frameWidth, frameHeight),
2063
2216
  });
2064
2217
  await client.send('Runtime.evaluate', {
2065
2218
  expression: `new Promise((resolve) => {
@@ -2076,21 +2229,52 @@ async function captureFramedScreenshot(
2076
2229
  });
2077
2230
 
2078
2231
  const framedScreenshot = await client.send('Page.captureScreenshot', {
2079
- format: 'png',
2232
+ format: profile.outputFormat || 'png',
2233
+ quality: profile.outputFormat === 'jpeg' ? 90 : undefined,
2080
2234
  fromSurface: true,
2081
2235
  captureBeyondViewport: false,
2082
2236
  });
2083
2237
 
2238
+ const framedScreenshotBuffer = Buffer.from(framedScreenshot.data, 'base64');
2239
+ if (!frameDimensions) {
2240
+ validateStoreImage(profile, frameWidth, frameHeight, framedScreenshotBuffer);
2241
+ }
2242
+
2084
2243
  fs.mkdirSync(framedProfileDirectory, { recursive: true });
2085
2244
  fs.writeFileSync(
2086
2245
  framedScreenshotPath,
2087
- Buffer.from(framedScreenshot.data, 'base64'),
2088
- { flag: 'wx' },
2246
+ framedScreenshotBuffer,
2247
+ { flag: skipExisting ? 'wx' : 'w' },
2089
2248
  );
2090
2249
  console.log(`Saved framed ${profile.label}: ${framedScreenshotPath}`);
2091
2250
  return true;
2092
2251
  }
2093
2252
 
2253
+ function validateStoreImage(profile, width, height, imageBuffer) {
2254
+ if (!profile.storeRequirements) return;
2255
+
2256
+ const {
2257
+ maxFileSizeBytes, minPixels, maxPixels, minAspectRatio, maxAspectRatio,
2258
+ } = profile.storeRequirements;
2259
+ const aspectRatio = width / height;
2260
+
2261
+ if (
2262
+ width < minPixels || height < minPixels
2263
+ || width > maxPixels || height > maxPixels
2264
+ || aspectRatio < minAspectRatio || aspectRatio > maxAspectRatio
2265
+ ) {
2266
+ throw new Error(
2267
+ `${profile.label} must be between ${minPixels}px and ${maxPixels}px with aspect ratio <= ${maxAspectRatio}:1; received ${width}x${height}.`,
2268
+ );
2269
+ }
2270
+
2271
+ if (imageBuffer.length > maxFileSizeBytes) {
2272
+ throw new Error(
2273
+ `${profile.label} exceeds ${maxFileSizeBytes / (1024 * 1024)} MB: ${Math.ceil(imageBuffer.length / (1024 * 1024))} MB.`,
2274
+ );
2275
+ }
2276
+ }
2277
+
2094
2278
  async function captureProfile(client, snapshot, profile, screenshotFileName) {
2095
2279
  await client.send('Emulation.setDeviceMetricsOverride', {
2096
2280
  width: profile.width,
@@ -2118,7 +2302,8 @@ async function captureProfile(client, snapshot, profile, screenshotFileName) {
2118
2302
  }
2119
2303
 
2120
2304
  const screenshot = await client.send('Page.captureScreenshot', {
2121
- format: 'png',
2305
+ format: profile.outputFormat || 'png',
2306
+ quality: profile.outputFormat === 'jpeg' ? 90 : undefined,
2122
2307
  fromSurface: true,
2123
2308
  captureBeyondViewport: false,
2124
2309
  });
@@ -2126,6 +2311,12 @@ async function captureProfile(client, snapshot, profile, screenshotFileName) {
2126
2311
  const profileDirectory = path.join(screenshotDirectory, profile.outputDirectory);
2127
2312
  const screenshotPath = path.join(profileDirectory, screenshotFileName);
2128
2313
  const screenshotBuffer = Buffer.from(screenshot.data, 'base64');
2314
+ validateStoreImage(
2315
+ profile,
2316
+ profile.width * profile.deviceScaleFactor,
2317
+ profile.height * profile.deviceScaleFactor,
2318
+ screenshotBuffer,
2319
+ );
2129
2320
 
2130
2321
  fs.mkdirSync(profileDirectory, { recursive: true });
2131
2322
  fs.writeFileSync(screenshotPath, screenshotBuffer, { flag: 'wx' });
@@ -2145,6 +2336,22 @@ async function captureScreenshotSet(
2145
2336
  selectedProfiles,
2146
2337
  fileNameInput,
2147
2338
  ) {
2339
+ const storeProfile = selectedProfiles.find((profile) => profile.storeRequirements);
2340
+ if (storeProfile) {
2341
+ const profileDirectory = path.join(screenshotDirectory, storeProfile.outputDirectory);
2342
+ const existingImageCount = fs.existsSync(profileDirectory)
2343
+ ? fs.readdirSync(profileDirectory, { withFileTypes: true })
2344
+ .filter((entry) => entry.isFile() && /\.(png|jpe?g|webp)$/i.test(entry.name))
2345
+ .length
2346
+ : 0;
2347
+
2348
+ if (existingImageCount >= storeProfile.storeRequirements.maxImages) {
2349
+ throw new Error(
2350
+ `${storeProfile.label} already contains ${existingImageCount} images; the store allows a maximum of ${storeProfile.storeRequirements.maxImages}.`,
2351
+ );
2352
+ }
2353
+ }
2354
+
2148
2355
  console.log('Capturing the current rendered page and resolved native data...');
2149
2356
  const snapshot = await capturePageSnapshot(deviceClient);
2150
2357
  const screenshotFileName = getScreenshotFileName(fileNameInput, selectedProfiles);
@@ -2213,6 +2420,148 @@ async function frameExistingScreenshots(selectedProfiles) {
2213
2420
  }
2214
2421
  }
2215
2422
 
2423
+ async function createVivoAssetsFromExistingScreenshots() {
2424
+ const vivoProfile = deviceProfiles.find((profile) => profile.name === 'vivo-store');
2425
+ const sourceDirectory = path.join(screenshotDirectory, 'Android');
2426
+ const outputDirectory = path.join(screenshotDirectory, vivoProfile.outputDirectory);
2427
+ const sourceFiles = fs.existsSync(sourceDirectory)
2428
+ ? fs.readdirSync(sourceDirectory, { withFileTypes: true })
2429
+ .filter((entry) => entry.isFile() && /\.png$/i.test(entry.name))
2430
+ .map((entry) => entry.name)
2431
+ .sort((first, second) => first.localeCompare(second, undefined, { numeric: true }))
2432
+ : [];
2433
+
2434
+ if (!sourceFiles.length) {
2435
+ throw new Error(`No existing PNG screenshots found in ${sourceDirectory}.`);
2436
+ }
2437
+
2438
+ const { maxImages } = vivoProfile.storeRequirements;
2439
+ const selectedSourceFiles = sourceFiles.slice(0, maxImages);
2440
+ if (sourceFiles.length > maxImages) {
2441
+ console.warn(
2442
+ `Vivo Store allows a maximum of ${maxImages} images; using the first ${maxImages} of ${sourceFiles.length} existing Android screenshots.`,
2443
+ );
2444
+ }
2445
+
2446
+ let replayBrowser;
2447
+ let replayClient;
2448
+ let createdCount = 0;
2449
+
2450
+ try {
2451
+ replayBrowser = await launchReplayBrowser();
2452
+ replayClient = await createReplayPage(replayBrowser);
2453
+ fs.mkdirSync(outputDirectory, { recursive: true });
2454
+
2455
+ for (const screenshotFileName of selectedSourceFiles) {
2456
+ const sourcePath = path.join(sourceDirectory, screenshotFileName);
2457
+ const outputPath = path.join(outputDirectory, screenshotFileName);
2458
+ const sourceBuffer = fs.readFileSync(sourcePath);
2459
+ const vivoBuffer = await sharp(sourceBuffer)
2460
+ .resize(vivoProfile.width * vivoProfile.deviceScaleFactor, vivoProfile.height * vivoProfile.deviceScaleFactor, {
2461
+ fit: 'contain',
2462
+ background: '#f5f7fc',
2463
+ })
2464
+ .png()
2465
+ .toBuffer();
2466
+
2467
+ validateStoreImage(
2468
+ vivoProfile,
2469
+ vivoProfile.width * vivoProfile.deviceScaleFactor,
2470
+ vivoProfile.height * vivoProfile.deviceScaleFactor,
2471
+ vivoBuffer,
2472
+ );
2473
+
2474
+ fs.writeFileSync(outputPath, vivoBuffer);
2475
+ createdCount += 1;
2476
+ console.log(`Saved Vivo frameless screenshot: ${outputPath}`);
2477
+
2478
+ await captureFramedScreenshot(
2479
+ replayClient,
2480
+ sourceBuffer,
2481
+ vivoProfile,
2482
+ screenshotFileName,
2483
+ false,
2484
+ );
2485
+ }
2486
+ } finally {
2487
+ if (replayClient) replayClient.close();
2488
+ if (replayBrowser) await closeReplayBrowser(replayBrowser);
2489
+ }
2490
+
2491
+ console.log(`Vivo assets created from existing screenshots: ${createdCount} frameless image(s).`);
2492
+ }
2493
+
2494
+ async function createOppoAssetsFromExistingScreenshots() {
2495
+ const oppoProfile = deviceProfiles.find((profile) => profile.name === 'oppo-store');
2496
+ const sourceDirectory = path.join(screenshotDirectory, 'Android');
2497
+ const outputDirectory = path.join(screenshotDirectory, oppoProfile.outputDirectory);
2498
+ const sourceFiles = fs.existsSync(sourceDirectory)
2499
+ ? fs.readdirSync(sourceDirectory, { withFileTypes: true })
2500
+ .filter((entry) => entry.isFile() && /\.png$/i.test(entry.name))
2501
+ .map((entry) => entry.name)
2502
+ .sort((first, second) => first.localeCompare(second, undefined, { numeric: true }))
2503
+ : [];
2504
+
2505
+ if (!sourceFiles.length) {
2506
+ throw new Error(`No existing PNG screenshots found in ${sourceDirectory}.`);
2507
+ }
2508
+
2509
+ const { maxImages } = oppoProfile.storeRequirements;
2510
+ const selectedSourceFiles = sourceFiles.slice(0, maxImages);
2511
+ if (sourceFiles.length > maxImages) {
2512
+ console.warn(
2513
+ `Oppo Store allows a maximum of ${maxImages} images; using the first ${maxImages} of ${sourceFiles.length} existing Android screenshots.`,
2514
+ );
2515
+ }
2516
+
2517
+ let replayBrowser;
2518
+ let replayClient;
2519
+ let createdCount = 0;
2520
+
2521
+ try {
2522
+ replayBrowser = await launchReplayBrowser();
2523
+ replayClient = await createReplayPage(replayBrowser);
2524
+ fs.mkdirSync(outputDirectory, { recursive: true });
2525
+
2526
+ for (const sourceFileName of selectedSourceFiles) {
2527
+ const outputFileName = `${path.basename(sourceFileName, path.extname(sourceFileName))}.jpg`;
2528
+ const sourceBuffer = fs.readFileSync(path.join(sourceDirectory, sourceFileName));
2529
+ const oppoBuffer = await sharp(sourceBuffer)
2530
+ .resize(oppoProfile.width * oppoProfile.deviceScaleFactor, oppoProfile.height * oppoProfile.deviceScaleFactor, {
2531
+ fit: 'contain',
2532
+ background: '#f5f7fc',
2533
+ })
2534
+ .jpeg({ quality: 90 })
2535
+ .toBuffer();
2536
+ const outputPath = path.join(outputDirectory, outputFileName);
2537
+
2538
+ validateStoreImage(
2539
+ oppoProfile,
2540
+ oppoProfile.width * oppoProfile.deviceScaleFactor,
2541
+ oppoProfile.height * oppoProfile.deviceScaleFactor,
2542
+ oppoBuffer,
2543
+ );
2544
+
2545
+ fs.writeFileSync(outputPath, oppoBuffer);
2546
+ createdCount += 1;
2547
+ console.log(`Saved Oppo frameless screenshot: ${outputPath}`);
2548
+
2549
+ await captureFramedScreenshot(
2550
+ replayClient,
2551
+ sourceBuffer,
2552
+ oppoProfile,
2553
+ outputFileName,
2554
+ false,
2555
+ );
2556
+ }
2557
+ } finally {
2558
+ if (replayClient) replayClient.close();
2559
+ if (replayBrowser) await closeReplayBrowser(replayBrowser);
2560
+ }
2561
+
2562
+ console.log(`Oppo assets created from existing screenshots: ${createdCount} frameless image(s).`);
2563
+ }
2564
+
2216
2565
  async function runInteractiveCaptureLoop(
2217
2566
  deviceClient,
2218
2567
  replayClient,
@@ -2254,6 +2603,8 @@ async function getInteractiveOptions(options) {
2254
2603
  || options.dryRun
2255
2604
  || options.amazonAssets
2256
2605
  || options.samsungAssets
2606
+ || options.vivoExisting
2607
+ || options.oppoExisting
2257
2608
  ) {
2258
2609
  return options;
2259
2610
  }
@@ -2332,6 +2683,16 @@ async function main() {
2332
2683
  return;
2333
2684
  }
2334
2685
 
2686
+ if (options.vivoExisting) {
2687
+ await createVivoAssetsFromExistingScreenshots();
2688
+ return;
2689
+ }
2690
+
2691
+ if (options.oppoExisting) {
2692
+ await createOppoAssetsFromExistingScreenshots();
2693
+ return;
2694
+ }
2695
+
2335
2696
  if (!options.dryRun) {
2336
2697
  await createMissingStoreAssets();
2337
2698
  }