codeplay-common 4.6.7 → 4.6.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.
@@ -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: 1000 * 1000,
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,6 +2182,9 @@ async function captureFramedScreenshot(
2040
2182
  return false;
2041
2183
  }
2042
2184
 
2185
+ const originalImageMetadata = profile.storeRequirements
2186
+ ? await sharp(screenshotBuffer).metadata()
2187
+ : null;
2043
2188
  const hasCustomFrameSize = Boolean(profile.framedWidth && profile.framedHeight);
2044
2189
  const frameWidth = profile.framedWidth || profile.width;
2045
2190
  const frameHeight = profile.framedHeight || profile.height;
@@ -2059,7 +2204,12 @@ async function captureFramedScreenshot(
2059
2204
  const frameTree = await client.send('Page.getFrameTree');
2060
2205
  await client.send('Page.setDocumentContent', {
2061
2206
  frameId: frameTree.frameTree.frame.id,
2062
- html: createDeviceFrameHtml(profile, screenshotBuffer),
2207
+ html: profile.name === 'vivo-store' || profile.name === 'oppo-store'
2208
+ ? createStoreDeviceFrameHtml(
2209
+ profile, screenshotBuffer, originalImageMetadata.width, originalImageMetadata.height,
2210
+ frameWidth, frameHeight, originalImageMetadata.format,
2211
+ )
2212
+ : createDeviceFrameHtml(profile, screenshotBuffer, frameWidth, frameHeight),
2063
2213
  });
2064
2214
  await client.send('Runtime.evaluate', {
2065
2215
  expression: `new Promise((resolve) => {
@@ -2076,21 +2226,50 @@ async function captureFramedScreenshot(
2076
2226
  });
2077
2227
 
2078
2228
  const framedScreenshot = await client.send('Page.captureScreenshot', {
2079
- format: 'png',
2229
+ format: profile.outputFormat || 'png',
2230
+ quality: profile.outputFormat === 'jpeg' ? 90 : undefined,
2080
2231
  fromSurface: true,
2081
2232
  captureBeyondViewport: false,
2082
2233
  });
2083
2234
 
2235
+ const framedScreenshotBuffer = Buffer.from(framedScreenshot.data, 'base64');
2236
+ validateStoreImage(profile, frameWidth, frameHeight, framedScreenshotBuffer);
2237
+
2084
2238
  fs.mkdirSync(framedProfileDirectory, { recursive: true });
2085
2239
  fs.writeFileSync(
2086
2240
  framedScreenshotPath,
2087
- Buffer.from(framedScreenshot.data, 'base64'),
2088
- { flag: 'wx' },
2241
+ framedScreenshotBuffer,
2242
+ { flag: skipExisting ? 'wx' : 'w' },
2089
2243
  );
2090
2244
  console.log(`Saved framed ${profile.label}: ${framedScreenshotPath}`);
2091
2245
  return true;
2092
2246
  }
2093
2247
 
2248
+ function validateStoreImage(profile, width, height, imageBuffer) {
2249
+ if (!profile.storeRequirements) return;
2250
+
2251
+ const {
2252
+ maxFileSizeBytes, minPixels, maxPixels, minAspectRatio, maxAspectRatio,
2253
+ } = profile.storeRequirements;
2254
+ const aspectRatio = width / height;
2255
+
2256
+ if (
2257
+ width < minPixels || height < minPixels
2258
+ || width > maxPixels || height > maxPixels
2259
+ || aspectRatio < minAspectRatio || aspectRatio > maxAspectRatio
2260
+ ) {
2261
+ throw new Error(
2262
+ `${profile.label} must be between ${minPixels}px and ${maxPixels}px with aspect ratio <= ${maxAspectRatio}:1; received ${width}x${height}.`,
2263
+ );
2264
+ }
2265
+
2266
+ if (imageBuffer.length > maxFileSizeBytes) {
2267
+ throw new Error(
2268
+ `${profile.label} exceeds ${maxFileSizeBytes / (1024 * 1024)} MB: ${Math.ceil(imageBuffer.length / (1024 * 1024))} MB.`,
2269
+ );
2270
+ }
2271
+ }
2272
+
2094
2273
  async function captureProfile(client, snapshot, profile, screenshotFileName) {
2095
2274
  await client.send('Emulation.setDeviceMetricsOverride', {
2096
2275
  width: profile.width,
@@ -2118,7 +2297,8 @@ async function captureProfile(client, snapshot, profile, screenshotFileName) {
2118
2297
  }
2119
2298
 
2120
2299
  const screenshot = await client.send('Page.captureScreenshot', {
2121
- format: 'png',
2300
+ format: profile.outputFormat || 'png',
2301
+ quality: profile.outputFormat === 'jpeg' ? 90 : undefined,
2122
2302
  fromSurface: true,
2123
2303
  captureBeyondViewport: false,
2124
2304
  });
@@ -2126,6 +2306,12 @@ async function captureProfile(client, snapshot, profile, screenshotFileName) {
2126
2306
  const profileDirectory = path.join(screenshotDirectory, profile.outputDirectory);
2127
2307
  const screenshotPath = path.join(profileDirectory, screenshotFileName);
2128
2308
  const screenshotBuffer = Buffer.from(screenshot.data, 'base64');
2309
+ validateStoreImage(
2310
+ profile,
2311
+ profile.width * profile.deviceScaleFactor,
2312
+ profile.height * profile.deviceScaleFactor,
2313
+ screenshotBuffer,
2314
+ );
2129
2315
 
2130
2316
  fs.mkdirSync(profileDirectory, { recursive: true });
2131
2317
  fs.writeFileSync(screenshotPath, screenshotBuffer, { flag: 'wx' });
@@ -2145,6 +2331,22 @@ async function captureScreenshotSet(
2145
2331
  selectedProfiles,
2146
2332
  fileNameInput,
2147
2333
  ) {
2334
+ const storeProfile = selectedProfiles.find((profile) => profile.storeRequirements);
2335
+ if (storeProfile) {
2336
+ const profileDirectory = path.join(screenshotDirectory, storeProfile.outputDirectory);
2337
+ const existingImageCount = fs.existsSync(profileDirectory)
2338
+ ? fs.readdirSync(profileDirectory, { withFileTypes: true })
2339
+ .filter((entry) => entry.isFile() && /\.(png|jpe?g|webp)$/i.test(entry.name))
2340
+ .length
2341
+ : 0;
2342
+
2343
+ if (existingImageCount >= storeProfile.storeRequirements.maxImages) {
2344
+ throw new Error(
2345
+ `${storeProfile.label} already contains ${existingImageCount} images; the store allows a maximum of ${storeProfile.storeRequirements.maxImages}.`,
2346
+ );
2347
+ }
2348
+ }
2349
+
2148
2350
  console.log('Capturing the current rendered page and resolved native data...');
2149
2351
  const snapshot = await capturePageSnapshot(deviceClient);
2150
2352
  const screenshotFileName = getScreenshotFileName(fileNameInput, selectedProfiles);
@@ -2213,6 +2415,148 @@ async function frameExistingScreenshots(selectedProfiles) {
2213
2415
  }
2214
2416
  }
2215
2417
 
2418
+ async function createVivoAssetsFromExistingScreenshots() {
2419
+ const vivoProfile = deviceProfiles.find((profile) => profile.name === 'vivo-store');
2420
+ const sourceDirectory = path.join(screenshotDirectory, 'Android');
2421
+ const outputDirectory = path.join(screenshotDirectory, vivoProfile.outputDirectory);
2422
+ const sourceFiles = fs.existsSync(sourceDirectory)
2423
+ ? fs.readdirSync(sourceDirectory, { withFileTypes: true })
2424
+ .filter((entry) => entry.isFile() && /\.png$/i.test(entry.name))
2425
+ .map((entry) => entry.name)
2426
+ .sort((first, second) => first.localeCompare(second, undefined, { numeric: true }))
2427
+ : [];
2428
+
2429
+ if (!sourceFiles.length) {
2430
+ throw new Error(`No existing PNG screenshots found in ${sourceDirectory}.`);
2431
+ }
2432
+
2433
+ const { maxImages } = vivoProfile.storeRequirements;
2434
+ const selectedSourceFiles = sourceFiles.slice(0, maxImages);
2435
+ if (sourceFiles.length > maxImages) {
2436
+ console.warn(
2437
+ `Vivo Store allows a maximum of ${maxImages} images; using the first ${maxImages} of ${sourceFiles.length} existing Android screenshots.`,
2438
+ );
2439
+ }
2440
+
2441
+ let replayBrowser;
2442
+ let replayClient;
2443
+ let createdCount = 0;
2444
+
2445
+ try {
2446
+ replayBrowser = await launchReplayBrowser();
2447
+ replayClient = await createReplayPage(replayBrowser);
2448
+ fs.mkdirSync(outputDirectory, { recursive: true });
2449
+
2450
+ for (const screenshotFileName of selectedSourceFiles) {
2451
+ const sourcePath = path.join(sourceDirectory, screenshotFileName);
2452
+ const outputPath = path.join(outputDirectory, screenshotFileName);
2453
+ const sourceBuffer = fs.readFileSync(sourcePath);
2454
+ const vivoBuffer = await sharp(sourceBuffer)
2455
+ .resize(vivoProfile.width * vivoProfile.deviceScaleFactor, vivoProfile.height * vivoProfile.deviceScaleFactor, {
2456
+ fit: 'contain',
2457
+ background: '#f5f7fc',
2458
+ })
2459
+ .png()
2460
+ .toBuffer();
2461
+
2462
+ validateStoreImage(
2463
+ vivoProfile,
2464
+ vivoProfile.width * vivoProfile.deviceScaleFactor,
2465
+ vivoProfile.height * vivoProfile.deviceScaleFactor,
2466
+ vivoBuffer,
2467
+ );
2468
+
2469
+ fs.writeFileSync(outputPath, vivoBuffer);
2470
+ createdCount += 1;
2471
+ console.log(`Saved Vivo frameless screenshot: ${outputPath}`);
2472
+
2473
+ await captureFramedScreenshot(
2474
+ replayClient,
2475
+ sourceBuffer,
2476
+ vivoProfile,
2477
+ screenshotFileName,
2478
+ false,
2479
+ );
2480
+ }
2481
+ } finally {
2482
+ if (replayClient) replayClient.close();
2483
+ if (replayBrowser) await closeReplayBrowser(replayBrowser);
2484
+ }
2485
+
2486
+ console.log(`Vivo assets created from existing screenshots: ${createdCount} frameless image(s).`);
2487
+ }
2488
+
2489
+ async function createOppoAssetsFromExistingScreenshots() {
2490
+ const oppoProfile = deviceProfiles.find((profile) => profile.name === 'oppo-store');
2491
+ const sourceDirectory = path.join(screenshotDirectory, 'Android');
2492
+ const outputDirectory = path.join(screenshotDirectory, oppoProfile.outputDirectory);
2493
+ const sourceFiles = fs.existsSync(sourceDirectory)
2494
+ ? fs.readdirSync(sourceDirectory, { withFileTypes: true })
2495
+ .filter((entry) => entry.isFile() && /\.png$/i.test(entry.name))
2496
+ .map((entry) => entry.name)
2497
+ .sort((first, second) => first.localeCompare(second, undefined, { numeric: true }))
2498
+ : [];
2499
+
2500
+ if (!sourceFiles.length) {
2501
+ throw new Error(`No existing PNG screenshots found in ${sourceDirectory}.`);
2502
+ }
2503
+
2504
+ const { maxImages } = oppoProfile.storeRequirements;
2505
+ const selectedSourceFiles = sourceFiles.slice(0, maxImages);
2506
+ if (sourceFiles.length > maxImages) {
2507
+ console.warn(
2508
+ `Oppo Store allows a maximum of ${maxImages} images; using the first ${maxImages} of ${sourceFiles.length} existing Android screenshots.`,
2509
+ );
2510
+ }
2511
+
2512
+ let replayBrowser;
2513
+ let replayClient;
2514
+ let createdCount = 0;
2515
+
2516
+ try {
2517
+ replayBrowser = await launchReplayBrowser();
2518
+ replayClient = await createReplayPage(replayBrowser);
2519
+ fs.mkdirSync(outputDirectory, { recursive: true });
2520
+
2521
+ for (const sourceFileName of selectedSourceFiles) {
2522
+ const outputFileName = `${path.basename(sourceFileName, path.extname(sourceFileName))}.jpg`;
2523
+ const sourceBuffer = fs.readFileSync(path.join(sourceDirectory, sourceFileName));
2524
+ const oppoBuffer = await sharp(sourceBuffer)
2525
+ .resize(oppoProfile.width * oppoProfile.deviceScaleFactor, oppoProfile.height * oppoProfile.deviceScaleFactor, {
2526
+ fit: 'contain',
2527
+ background: '#f5f7fc',
2528
+ })
2529
+ .jpeg({ quality: 90 })
2530
+ .toBuffer();
2531
+ const outputPath = path.join(outputDirectory, outputFileName);
2532
+
2533
+ validateStoreImage(
2534
+ oppoProfile,
2535
+ oppoProfile.width * oppoProfile.deviceScaleFactor,
2536
+ oppoProfile.height * oppoProfile.deviceScaleFactor,
2537
+ oppoBuffer,
2538
+ );
2539
+
2540
+ fs.writeFileSync(outputPath, oppoBuffer);
2541
+ createdCount += 1;
2542
+ console.log(`Saved Oppo frameless screenshot: ${outputPath}`);
2543
+
2544
+ await captureFramedScreenshot(
2545
+ replayClient,
2546
+ sourceBuffer,
2547
+ oppoProfile,
2548
+ outputFileName,
2549
+ false,
2550
+ );
2551
+ }
2552
+ } finally {
2553
+ if (replayClient) replayClient.close();
2554
+ if (replayBrowser) await closeReplayBrowser(replayBrowser);
2555
+ }
2556
+
2557
+ console.log(`Oppo assets created from existing screenshots: ${createdCount} frameless image(s).`);
2558
+ }
2559
+
2216
2560
  async function runInteractiveCaptureLoop(
2217
2561
  deviceClient,
2218
2562
  replayClient,
@@ -2254,6 +2598,8 @@ async function getInteractiveOptions(options) {
2254
2598
  || options.dryRun
2255
2599
  || options.amazonAssets
2256
2600
  || options.samsungAssets
2601
+ || options.vivoExisting
2602
+ || options.oppoExisting
2257
2603
  ) {
2258
2604
  return options;
2259
2605
  }
@@ -2332,6 +2678,16 @@ async function main() {
2332
2678
  return;
2333
2679
  }
2334
2680
 
2681
+ if (options.vivoExisting) {
2682
+ await createVivoAssetsFromExistingScreenshots();
2683
+ return;
2684
+ }
2685
+
2686
+ if (options.oppoExisting) {
2687
+ await createOppoAssetsFromExistingScreenshots();
2688
+ return;
2689
+ }
2690
+
2335
2691
  if (!options.dryRun) {
2336
2692
  await createMissingStoreAssets();
2337
2693
  }