appshot-cli 0.1.0

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.
Files changed (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +381 -0
  3. package/bin/appshot.js +5 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +31 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/build.d.ts +3 -0
  9. package/dist/commands/build.d.ts.map +1 -0
  10. package/dist/commands/build.js +200 -0
  11. package/dist/commands/build.js.map +1 -0
  12. package/dist/commands/caption.d.ts +3 -0
  13. package/dist/commands/caption.d.ts.map +1 -0
  14. package/dist/commands/caption.js +118 -0
  15. package/dist/commands/caption.js.map +1 -0
  16. package/dist/commands/check.d.ts +3 -0
  17. package/dist/commands/check.d.ts.map +1 -0
  18. package/dist/commands/check.js +121 -0
  19. package/dist/commands/check.js.map +1 -0
  20. package/dist/commands/clean.d.ts +3 -0
  21. package/dist/commands/clean.d.ts.map +1 -0
  22. package/dist/commands/clean.js +133 -0
  23. package/dist/commands/clean.js.map +1 -0
  24. package/dist/commands/init.d.ts +3 -0
  25. package/dist/commands/init.d.ts.map +1 -0
  26. package/dist/commands/init.js +84 -0
  27. package/dist/commands/init.js.map +1 -0
  28. package/dist/commands/localize.d.ts +3 -0
  29. package/dist/commands/localize.d.ts.map +1 -0
  30. package/dist/commands/localize.js +31 -0
  31. package/dist/commands/localize.js.map +1 -0
  32. package/dist/commands/presets.d.ts +3 -0
  33. package/dist/commands/presets.d.ts.map +1 -0
  34. package/dist/commands/presets.js +135 -0
  35. package/dist/commands/presets.js.map +1 -0
  36. package/dist/commands/specs.d.ts +3 -0
  37. package/dist/commands/specs.d.ts.map +1 -0
  38. package/dist/commands/specs.js +69 -0
  39. package/dist/commands/specs.js.map +1 -0
  40. package/dist/commands/validate.d.ts +3 -0
  41. package/dist/commands/validate.d.ts.map +1 -0
  42. package/dist/commands/validate.js +183 -0
  43. package/dist/commands/validate.js.map +1 -0
  44. package/dist/core/app-store-specs.d.ts +55 -0
  45. package/dist/core/app-store-specs.d.ts.map +1 -0
  46. package/dist/core/app-store-specs.js +457 -0
  47. package/dist/core/app-store-specs.js.map +1 -0
  48. package/dist/core/compose.d.ts +30 -0
  49. package/dist/core/compose.d.ts.map +1 -0
  50. package/dist/core/compose.js +420 -0
  51. package/dist/core/compose.js.map +1 -0
  52. package/dist/core/devices.d.ts +50 -0
  53. package/dist/core/devices.d.ts.map +1 -0
  54. package/dist/core/devices.js +404 -0
  55. package/dist/core/devices.js.map +1 -0
  56. package/dist/core/files.d.ts +5 -0
  57. package/dist/core/files.d.ts.map +1 -0
  58. package/dist/core/files.js +35 -0
  59. package/dist/core/files.js.map +1 -0
  60. package/dist/core/frames-analyzer.d.ts +24 -0
  61. package/dist/core/frames-analyzer.d.ts.map +1 -0
  62. package/dist/core/frames-analyzer.js +97 -0
  63. package/dist/core/frames-analyzer.js.map +1 -0
  64. package/dist/core/frames-loader.d.ts +113 -0
  65. package/dist/core/frames-loader.d.ts.map +1 -0
  66. package/dist/core/frames-loader.js +440 -0
  67. package/dist/core/frames-loader.js.map +1 -0
  68. package/dist/core/mask-generator.d.ts +10 -0
  69. package/dist/core/mask-generator.d.ts.map +1 -0
  70. package/dist/core/mask-generator.js +99 -0
  71. package/dist/core/mask-generator.js.map +1 -0
  72. package/dist/core/render.d.ts +10 -0
  73. package/dist/core/render.d.ts.map +1 -0
  74. package/dist/core/render.js +92 -0
  75. package/dist/core/render.js.map +1 -0
  76. package/dist/core/text-renderer.d.ts +18 -0
  77. package/dist/core/text-renderer.d.ts.map +1 -0
  78. package/dist/core/text-renderer.js +148 -0
  79. package/dist/core/text-renderer.js.map +1 -0
  80. package/dist/types.d.ts +57 -0
  81. package/dist/types.d.ts.map +1 -0
  82. package/dist/types.js +2 -0
  83. package/dist/types.js.map +1 -0
  84. package/dist/utils/caption-history.d.ts +35 -0
  85. package/dist/utils/caption-history.d.ts.map +1 -0
  86. package/dist/utils/caption-history.js +233 -0
  87. package/dist/utils/caption-history.js.map +1 -0
  88. package/frames/Frames.json +308 -0
  89. package/frames/MacBook Air 2020.png +0 -0
  90. package/frames/MacBook Air 2022.png +0 -0
  91. package/frames/MacBook Pro 13.png +0 -0
  92. package/frames/MacBook Pro 2021 14.png +0 -0
  93. package/frames/MacBook Pro 2021 16.png +0 -0
  94. package/frames/Watch Series 10 42.png +0 -0
  95. package/frames/Watch Series 10 42_mask.png +0 -0
  96. package/frames/Watch Series 10 46.png +0 -0
  97. package/frames/Watch Series 10 46_mask.png +0 -0
  98. package/frames/Watch Series 4 40.png +0 -0
  99. package/frames/Watch Series 4 44.png +0 -0
  100. package/frames/Watch Series 7 41.png +0 -0
  101. package/frames/Watch Series 7 45.png +0 -0
  102. package/frames/Watch Ultra 2024.png +0 -0
  103. package/frames/iMac 2021.png +0 -0
  104. package/frames/iPad 2021 Landscape.png +0 -0
  105. package/frames/iPad 2021 Portrait.png +0 -0
  106. package/frames/iPad Air 2020 Landscape.png +0 -0
  107. package/frames/iPad Air 2020 Portrait.png +0 -0
  108. package/frames/iPad Pro 2018-2021 11 Landscape.png +0 -0
  109. package/frames/iPad Pro 2018-2021 11 Portrait.png +0 -0
  110. package/frames/iPad Pro 2018-2021 Landscape.png +0 -0
  111. package/frames/iPad Pro 2018-2021 Portrait.png +0 -0
  112. package/frames/iPad Pro 2024 11 Landscape.png +0 -0
  113. package/frames/iPad Pro 2024 11 Portrait.png +0 -0
  114. package/frames/iPad Pro 2024 13 Landscape.png +0 -0
  115. package/frames/iPad Pro 2024 13 Portrait.png +0 -0
  116. package/frames/iPad mini 2021 Landscape.png +0 -0
  117. package/frames/iPad mini 2021 Portrait.png +0 -0
  118. package/frames/iPhone 11 Landscape.png +0 -0
  119. package/frames/iPhone 11 Portrait.png +0 -0
  120. package/frames/iPhone 11 Pro Max Landscape.png +0 -0
  121. package/frames/iPhone 11 Pro Max Portrait.png +0 -0
  122. package/frames/iPhone 11 Pro Portrait.png +0 -0
  123. package/frames/iPhone 12-13 Pro Landscape.png +0 -0
  124. package/frames/iPhone 12-13 Pro Max Landscape.png +0 -0
  125. package/frames/iPhone 12-13 Pro Max Landscape_mask.png +0 -0
  126. package/frames/iPhone 12-13 Pro Max Portrait.png +0 -0
  127. package/frames/iPhone 12-13 Pro Max Portrait_mask.png +0 -0
  128. package/frames/iPhone 12-13 Pro Portrait.png +0 -0
  129. package/frames/iPhone 12-13 mini Landscape.png +0 -0
  130. package/frames/iPhone 12-13 mini Portrait.png +0 -0
  131. package/frames/iPhone 16 Landscape.png +0 -0
  132. package/frames/iPhone 16 Landscape_frame_no_island.png +0 -0
  133. package/frames/iPhone 16 Landscape_mask.png +0 -0
  134. package/frames/iPhone 16 Plus Landscape.png +0 -0
  135. package/frames/iPhone 16 Plus Landscape_frame_no_island.png +0 -0
  136. package/frames/iPhone 16 Plus Landscape_mask.png +0 -0
  137. package/frames/iPhone 16 Plus Portrait.png +0 -0
  138. package/frames/iPhone 16 Plus Portrait_frame_no_island.png +0 -0
  139. package/frames/iPhone 16 Plus Portrait_mask.png +0 -0
  140. package/frames/iPhone 16 Portrait.png +0 -0
  141. package/frames/iPhone 16 Portrait_frame_no_island.png +0 -0
  142. package/frames/iPhone 16 Portrait_mask.png +0 -0
  143. package/frames/iPhone 16 Pro Landscape.png +0 -0
  144. package/frames/iPhone 16 Pro Landscape_frame_no_island.png +0 -0
  145. package/frames/iPhone 16 Pro Landscape_mask.png +0 -0
  146. package/frames/iPhone 16 Pro Max Landscape.png +0 -0
  147. package/frames/iPhone 16 Pro Max Landscape_frame_no_island.png +0 -0
  148. package/frames/iPhone 16 Pro Max Landscape_mask.png +0 -0
  149. package/frames/iPhone 16 Pro Max Portrait.png +0 -0
  150. package/frames/iPhone 16 Pro Max Portrait_frame_no_island.png +0 -0
  151. package/frames/iPhone 16 Pro Max Portrait_mask.png +0 -0
  152. package/frames/iPhone 16 Pro Portrait.png +0 -0
  153. package/frames/iPhone 16 Pro Portrait_frame_no_island.png +0 -0
  154. package/frames/iPhone 16 Pro Portrait_mask.png +0 -0
  155. package/frames/iPhone 8 Plus Landscape.png +0 -0
  156. package/frames/iPhone 8 Plus Portrait.png +0 -0
  157. 2020 SE.png +0 -0
  158. package/frames/version.txt +1 -0
  159. package/package.json +61 -0
@@ -0,0 +1,404 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import sharp from 'sharp';
4
+ import { buildFrameRegistry } from './frames-loader.js';
5
+ // Dynamic frame registry - will be populated from Frames.json
6
+ export let frameRegistry = [
7
+ // iPhone frames
8
+ {
9
+ name: 'iphone-15-pro-max-portrait',
10
+ displayName: 'iPhone 15 Pro Max',
11
+ orientation: 'portrait',
12
+ frameWidth: 1490,
13
+ frameHeight: 3096,
14
+ screenRect: { x: 100, y: 150, width: 1290, height: 2796 },
15
+ deviceType: 'iphone'
16
+ },
17
+ {
18
+ name: 'iphone-15-pro-max-landscape',
19
+ displayName: 'iPhone 15 Pro Max',
20
+ orientation: 'landscape',
21
+ frameWidth: 3096,
22
+ frameHeight: 1490,
23
+ screenRect: { x: 150, y: 100, width: 2796, height: 1290 },
24
+ deviceType: 'iphone'
25
+ },
26
+ {
27
+ name: 'iphone-15-pro-portrait',
28
+ displayName: 'iPhone 15 Pro',
29
+ orientation: 'portrait',
30
+ frameWidth: 1379,
31
+ frameHeight: 2856,
32
+ screenRect: { x: 100, y: 150, width: 1179, height: 2556 },
33
+ deviceType: 'iphone'
34
+ },
35
+ {
36
+ name: 'iphone-se-portrait',
37
+ displayName: 'iPhone SE',
38
+ orientation: 'portrait',
39
+ frameWidth: 950,
40
+ frameHeight: 1634,
41
+ screenRect: { x: 100, y: 150, width: 750, height: 1334 },
42
+ deviceType: 'iphone'
43
+ },
44
+ // iPad frames
45
+ {
46
+ name: 'ipad-pro-12-portrait',
47
+ displayName: 'iPad Pro 12.9"',
48
+ orientation: 'portrait',
49
+ frameWidth: 2248,
50
+ frameHeight: 3032,
51
+ screenRect: { x: 100, y: 150, width: 2048, height: 2732 },
52
+ deviceType: 'ipad'
53
+ },
54
+ {
55
+ name: 'ipad-pro-12-landscape',
56
+ displayName: 'iPad Pro 12.9"',
57
+ orientation: 'landscape',
58
+ frameWidth: 3032,
59
+ frameHeight: 2248,
60
+ screenRect: { x: 150, y: 100, width: 2732, height: 2048 },
61
+ deviceType: 'ipad'
62
+ },
63
+ {
64
+ name: 'ipad-pro-11-portrait',
65
+ displayName: 'iPad Pro 11"',
66
+ orientation: 'portrait',
67
+ frameWidth: 1868,
68
+ frameHeight: 2688,
69
+ screenRect: { x: 100, y: 150, width: 1668, height: 2388 },
70
+ deviceType: 'ipad'
71
+ },
72
+ {
73
+ name: 'ipad-pro-11-landscape',
74
+ displayName: 'iPad Pro 11"',
75
+ orientation: 'landscape',
76
+ frameWidth: 2688,
77
+ frameHeight: 1868,
78
+ screenRect: { x: 150, y: 100, width: 2388, height: 1668 },
79
+ deviceType: 'ipad'
80
+ },
81
+ // Mac frames (always landscape)
82
+ {
83
+ name: 'macbook-pro-16',
84
+ displayName: 'MacBook Pro 16"',
85
+ orientation: 'landscape',
86
+ frameWidth: 3856,
87
+ frameHeight: 2434,
88
+ screenRect: { x: 200, y: 100, width: 3456, height: 2234 },
89
+ deviceType: 'mac'
90
+ },
91
+ {
92
+ name: 'macbook-air-15',
93
+ displayName: 'MacBook Air 15"',
94
+ orientation: 'landscape',
95
+ frameWidth: 3280,
96
+ frameHeight: 2064,
97
+ screenRect: { x: 200, y: 100, width: 2880, height: 1864 },
98
+ deviceType: 'mac'
99
+ },
100
+ {
101
+ name: 'imac-24',
102
+ displayName: 'iMac 24"',
103
+ orientation: 'landscape',
104
+ frameWidth: 4880,
105
+ frameHeight: 2920,
106
+ screenRect: { x: 200, y: 200, width: 4480, height: 2520 },
107
+ deviceType: 'mac'
108
+ },
109
+ // Watch frames (always portrait)
110
+ {
111
+ name: 'watch-ultra-2',
112
+ displayName: 'Apple Watch Ultra 2',
113
+ orientation: 'portrait',
114
+ frameWidth: 610,
115
+ frameHeight: 702,
116
+ screenRect: { x: 100, y: 100, width: 410, height: 502 },
117
+ deviceType: 'watch'
118
+ },
119
+ {
120
+ name: 'watch-series-9-45mm',
121
+ displayName: 'Apple Watch Series 9 (45mm)',
122
+ orientation: 'portrait',
123
+ frameWidth: 596,
124
+ frameHeight: 684,
125
+ screenRect: { x: 100, y: 100, width: 396, height: 484 },
126
+ deviceType: 'watch'
127
+ }
128
+ ];
129
+ /**
130
+ * Detect orientation from image dimensions
131
+ */
132
+ export function detectOrientation(width, height) {
133
+ return width > height ? 'landscape' : 'portrait';
134
+ }
135
+ /**
136
+ * Get image dimensions from file
137
+ */
138
+ export async function getImageDimensions(imagePath) {
139
+ const metadata = await sharp(imagePath).metadata();
140
+ const width = metadata.width || 0;
141
+ const height = metadata.height || 0;
142
+ const orientation = detectOrientation(width, height);
143
+ return { width, height, orientation };
144
+ }
145
+ // Map exact resolutions to specific devices
146
+ const RESOLUTION_TO_DEVICE = {
147
+ // iPhone resolutions (portrait)
148
+ '1320x2868': 'iphone-16-pro-max',
149
+ '1206x2622': 'iphone-16-pro',
150
+ '1290x2796': 'iphone-15-pro-max',
151
+ '1179x2556': 'iphone-15-pro',
152
+ '1284x2778': 'iphone-14-plus',
153
+ '1170x2532': 'iphone-14',
154
+ '1125x2436': 'iphone-11-pro',
155
+ '1242x2688': 'iphone-11-pro-max',
156
+ '828x1792': 'iphone-11',
157
+ '1080x2340': 'iphone-12-mini',
158
+ '750x1334': 'iphone-8-and-2020-se',
159
+ // iPhone resolutions (landscape)
160
+ '2868x1320': 'iphone-16-pro-max-landscape',
161
+ '2622x1206': 'iphone-16-pro-landscape',
162
+ '2796x1290': 'iphone-15-pro-max-landscape',
163
+ '2556x1179': 'iphone-15-pro-landscape',
164
+ '2778x1284': 'iphone-14-plus-landscape',
165
+ '2532x1170': 'iphone-14-landscape',
166
+ '2436x1125': 'iphone-11-pro-landscape',
167
+ '2688x1242': 'iphone-11-pro-max-landscape',
168
+ '1792x828': 'iphone-11-landscape',
169
+ '2340x1080': 'iphone-12-mini-landscape',
170
+ // iPad resolutions (portrait)
171
+ '2048x2732': 'ipad pro 2018 2021', // iPad Pro 12.9"
172
+ '1668x2388': 'ipad pro 2018 2021 11', // iPad Pro 11"
173
+ '1640x2360': 'ipad air 2020', // iPad Air
174
+ '1620x2160': 'ipad 2021', // Regular iPad & iPad mini (same resolution)
175
+ '2064x2752': 'ipad pro 2024 11', // iPad Pro 11" M4
176
+ '2420x3212': 'ipad pro 2024 13', // iPad Pro 13" M4
177
+ // iPad resolutions (landscape)
178
+ '2732x2048': 'ipad pro 2018 2021', // iPad Pro 12.9"
179
+ '2388x1668': 'ipad pro 2018 2021 11', // iPad Pro 11"
180
+ '2360x1640': 'ipad air 2020', // iPad Air
181
+ '2160x1620': 'ipad 2021', // Regular iPad & iPad mini (same resolution)
182
+ '2752x2064': 'ipad pro 2024 13', // iPad Pro 13" M4
183
+ '3212x2420': 'ipad pro 2024 13', // iPad Pro 13" M4
184
+ // Mac resolutions
185
+ '3456x2234': 'macbook-pro-16',
186
+ '3024x1964': 'macbook-pro-14',
187
+ '2880x1864': 'macbook-air-15',
188
+ '2560x1664': 'macbook-air-13',
189
+ '4480x2520': 'imac-24',
190
+ // Watch resolutions
191
+ '410x502': 'watch-ultra',
192
+ '396x484': 'watch-series-9-45mm',
193
+ '368x448': 'watch-series-9-41mm'
194
+ };
195
+ /**
196
+ * Detect exact device from screenshot resolution
197
+ */
198
+ function detectExactDevice(width, height) {
199
+ const key = `${width}x${height}`;
200
+ return RESOLUTION_TO_DEVICE[key] || null;
201
+ }
202
+ /**
203
+ * Find best matching frame for a screenshot
204
+ */
205
+ export function findBestFrame(screenshotWidth, screenshotHeight, deviceType, preferredFrame) {
206
+ const orientation = detectOrientation(screenshotWidth, screenshotHeight);
207
+ // If preferred frame specified, check if it matches orientation first
208
+ if (preferredFrame) {
209
+ const preferred = frameRegistry.find(f => f.name === preferredFrame);
210
+ if (preferred) {
211
+ // Warn if orientation mismatch
212
+ if (preferred.orientation !== orientation) {
213
+ console.warn(`Warning: Preferred frame '${preferredFrame}' is ${preferred.orientation} but screenshot is ${orientation}`);
214
+ // Don't use mismatched frame
215
+ }
216
+ else if (preferred.deviceType === deviceType) {
217
+ return preferred;
218
+ }
219
+ }
220
+ }
221
+ // Try exact resolution matching
222
+ const exactDevice = detectExactDevice(screenshotWidth, screenshotHeight);
223
+ if (exactDevice) {
224
+ console.log(` Detected exact device: ${exactDevice} from resolution ${screenshotWidth}x${screenshotHeight}`);
225
+ // Find frame that matches this exact device AND orientation
226
+ let exactFrame = frameRegistry.find(f => {
227
+ const normalizedName = f.name.toLowerCase().replace(/-/g, ' ');
228
+ const searchName = exactDevice.toLowerCase().replace(/-/g, ' ');
229
+ const nameMatches = normalizedName.includes(searchName) ||
230
+ (f.originalName && f.originalName.toLowerCase().includes(searchName));
231
+ // Must match both name AND orientation
232
+ return nameMatches && f.orientation === orientation;
233
+ });
234
+ // Special case: For 2752x2064 (iPad Pro 13" M4), ensure we don't get the 11" frame
235
+ if (screenshotWidth === 2752 && screenshotHeight === 2064) {
236
+ // Try to find iPad Pro 2024 13 Landscape frame first
237
+ const ipad13Frame = frameRegistry.find(f => (f.displayName === 'iPad Pro 2024 13 Landscape' ||
238
+ f.originalName === 'iPad Pro 2024 13 Landscape') &&
239
+ f.orientation === 'landscape');
240
+ if (ipad13Frame) {
241
+ exactFrame = ipad13Frame;
242
+ }
243
+ else {
244
+ // Fall back to the larger 12.9" frame if 13" not found
245
+ exactFrame = frameRegistry.find(f => f.displayName === 'iPad Pro 2018-2021 Landscape' &&
246
+ f.orientation === 'landscape' &&
247
+ !f.displayName.includes('11'));
248
+ }
249
+ }
250
+ if (exactFrame) {
251
+ console.log(` Found exact frame: ${exactFrame.displayName}`);
252
+ return exactFrame;
253
+ }
254
+ }
255
+ // Find frames matching device type and orientation
256
+ const candidates = frameRegistry.filter(f => f.deviceType === deviceType &&
257
+ f.orientation === orientation);
258
+ if (candidates.length === 0) {
259
+ console.warn(`No ${orientation} frames found for ${deviceType}. Frame will be skipped.`);
260
+ return null;
261
+ }
262
+ // Calculate aspect ratio of screenshot
263
+ const aspectRatio = screenshotWidth / screenshotHeight;
264
+ // Find frame with exact resolution match first
265
+ for (const frame of candidates) {
266
+ if (frame.screenRect.width === screenshotWidth &&
267
+ frame.screenRect.height === screenshotHeight) {
268
+ console.log(` Found exact resolution match: ${frame.displayName}`);
269
+ return frame;
270
+ }
271
+ }
272
+ // Otherwise find frame with closest aspect ratio match
273
+ let bestFrame = candidates[0];
274
+ let bestDiff = Math.abs((bestFrame.screenRect.width / bestFrame.screenRect.height) - aspectRatio);
275
+ for (const frame of candidates) {
276
+ const frameAspectRatio = frame.screenRect.width / frame.screenRect.height;
277
+ const diff = Math.abs(frameAspectRatio - aspectRatio);
278
+ if (diff < bestDiff) {
279
+ bestDiff = diff;
280
+ bestFrame = frame;
281
+ }
282
+ }
283
+ // Warn if aspect ratio is significantly different (>10% difference)
284
+ const finalAspectRatio = bestFrame.screenRect.width / bestFrame.screenRect.height;
285
+ const percentDiff = Math.abs(finalAspectRatio - aspectRatio) / aspectRatio * 100;
286
+ if (percentDiff > 10) {
287
+ console.warn(`Warning: Best matching frame has ${percentDiff.toFixed(1)}% aspect ratio difference`);
288
+ }
289
+ return bestFrame;
290
+ }
291
+ /**
292
+ * Get the bundled frames directory path
293
+ */
294
+ function getBundledFramesPath() {
295
+ // When running from installed package, frames are in node_modules/appshot/frames
296
+ // When running in development, frames are in the project root
297
+ const dirname = path.dirname(new URL(import.meta.url).pathname);
298
+ const projectRoot = path.resolve(dirname, '..', '..');
299
+ return path.join(projectRoot, 'frames');
300
+ }
301
+ /**
302
+ * Initialize frame registry from Frames.json if available
303
+ */
304
+ export async function initializeFrameRegistry(framesDir) {
305
+ let effectiveFramesDir = framesDir;
306
+ try {
307
+ // First try the configured frames directory
308
+ const framesJsonPath = path.join(framesDir, 'Frames.json');
309
+ await fs.access(framesJsonPath);
310
+ console.log('Loading frames from project directory...');
311
+ }
312
+ catch {
313
+ // Fall back to bundled frames
314
+ const bundledFramesDir = getBundledFramesPath();
315
+ try {
316
+ const bundledFramesJsonPath = path.join(bundledFramesDir, 'Frames.json');
317
+ await fs.access(bundledFramesJsonPath);
318
+ effectiveFramesDir = bundledFramesDir;
319
+ console.log('Using bundled frames (project frames not found)...');
320
+ }
321
+ catch {
322
+ // No frames available, use default registry
323
+ console.log('Using default frame registry');
324
+ return;
325
+ }
326
+ }
327
+ // Load frames from the effective directory
328
+ try {
329
+ const dynamicRegistry = await buildFrameRegistry(effectiveFramesDir);
330
+ if (dynamicRegistry.length > 0) {
331
+ frameRegistry = dynamicRegistry;
332
+ console.log(`Loaded ${frameRegistry.length} frames from ${effectiveFramesDir === framesDir ? 'project' : 'bundled'} Frames.json`);
333
+ }
334
+ }
335
+ catch (error) {
336
+ console.error('Failed to build frame registry:', error);
337
+ console.log('Using default frame registry');
338
+ }
339
+ }
340
+ /**
341
+ * Load frame image from disk
342
+ */
343
+ export async function loadFrame(framePath, frameName) {
344
+ // First try to find frame by originalName (for Frames.json compatibility)
345
+ const frame = frameRegistry.find(f => f.name === frameName);
346
+ const fileName = frame?.originalName || frameName;
347
+ // Try loading from provided path first
348
+ const tryLoadFrom = async (basePath) => {
349
+ try {
350
+ // Try with .png extension
351
+ let fullPath = path.join(basePath, `${fileName}.png`);
352
+ try {
353
+ const buffer = await fs.readFile(fullPath);
354
+ return buffer;
355
+ }
356
+ catch {
357
+ // Try without modification (in case the name already has extension)
358
+ fullPath = path.join(basePath, fileName);
359
+ const buffer = await fs.readFile(fullPath);
360
+ return buffer;
361
+ }
362
+ }
363
+ catch {
364
+ return null;
365
+ }
366
+ };
367
+ // Try provided frames directory first
368
+ let result = await tryLoadFrom(framePath);
369
+ if (result)
370
+ return result;
371
+ // Fall back to bundled frames
372
+ const bundledFramesDir = getBundledFramesPath();
373
+ if (bundledFramesDir !== framePath) {
374
+ result = await tryLoadFrom(bundledFramesDir);
375
+ if (result)
376
+ return result;
377
+ }
378
+ console.error(`ERROR: Could not load frame image: ${frameName} (tried as ${fileName})`);
379
+ console.error(` Looked in: ${framePath}`);
380
+ console.error(` Also tried: ${getBundledFramesPath()}`);
381
+ return null;
382
+ }
383
+ /**
384
+ * Auto-detect and load appropriate frame for a screenshot
385
+ */
386
+ export async function autoSelectFrame(screenshotPath, framesDir, deviceType, preferredFrame) {
387
+ try {
388
+ // Get screenshot dimensions
389
+ const { width, height } = await getImageDimensions(screenshotPath);
390
+ // Find best matching frame
391
+ const frameMetadata = findBestFrame(width, height, deviceType, preferredFrame);
392
+ if (!frameMetadata) {
393
+ return { frame: null, metadata: null };
394
+ }
395
+ // Try to load the frame image
396
+ const frame = await loadFrame(framesDir, frameMetadata.name);
397
+ return { frame, metadata: frameMetadata };
398
+ }
399
+ catch (error) {
400
+ console.error('Error auto-selecting frame:', error);
401
+ return { frame: null, metadata: null };
402
+ }
403
+ }
404
+ //# sourceMappingURL=devices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devices.js","sourceRoot":"","sources":["../../src/core/devices.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { AppshotConfig, CaptionsFile } from '../types.js';
2
+ export declare function loadConfig(): Promise<AppshotConfig>;
3
+ export declare function loadCaptions(captionsPath: string): Promise<CaptionsFile>;
4
+ export declare function fileExists(filePath: string): Promise<boolean>;
5
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/core/files.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE/D,wBAAsB,UAAU,IAAI,OAAO,CAAC,aAAa,CAAC,CAYzD;AAED,wBAAsB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAQ9E;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnE"}
@@ -0,0 +1,35 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ export async function loadConfig() {
4
+ const configPath = path.join(process.cwd(), '.appshot', 'config.json');
5
+ try {
6
+ const content = await fs.readFile(configPath, 'utf8');
7
+ return JSON.parse(content);
8
+ }
9
+ catch (error) {
10
+ if (error.code === 'ENOENT') {
11
+ throw new Error('Configuration not found. Run "appshot init" first.\n(Looking for .appshot/config.json)');
12
+ }
13
+ throw new Error(`Failed to load configuration: ${error instanceof Error ? error.message : String(error)}`);
14
+ }
15
+ }
16
+ export async function loadCaptions(captionsPath) {
17
+ try {
18
+ const content = await fs.readFile(captionsPath, 'utf8');
19
+ return JSON.parse(content);
20
+ }
21
+ catch {
22
+ // Return empty object if file doesn't exist or is invalid
23
+ return {};
24
+ }
25
+ }
26
+ export async function fileExists(filePath) {
27
+ try {
28
+ await fs.access(filePath);
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/core/files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;AAGxB,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;IAEvE,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACtD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAkB,CAAC;IAC9C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvD,MAAM,IAAI,KAAK,CAAC,wFAAwF,CAAC,CAAC;QAC5G,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,iCAAiC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC7G,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,YAAoB;IACrD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QACxD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAiB,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,0DAA0D;QAC1D,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Analyze a frame image to detect the transparent/screen area
3
+ * This helps us automatically determine where the screenshot should be placed
4
+ */
5
+ export declare function analyzeFrameScreenArea(framePath: string): Promise<{
6
+ x: number;
7
+ y: number;
8
+ width: number;
9
+ height: number;
10
+ } | null>;
11
+ /**
12
+ * Get screen area from Frames.json data or analyze the frame
13
+ */
14
+ export declare function getScreenArea(framePath: string, frameData: {
15
+ x?: string;
16
+ y?: string;
17
+ name: string;
18
+ }): Promise<{
19
+ x: number;
20
+ y: number;
21
+ width: number;
22
+ height: number;
23
+ }>;
24
+ //# sourceMappingURL=frames-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frames-analyzer.d.ts","sourceRoot":"","sources":["../../src/core/frames-analyzer.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IACvE,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,IAAI,CAAC,CA4ER;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE;IAAE,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClD,OAAO,CAAC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAwBlE"}
@@ -0,0 +1,97 @@
1
+ import sharp from 'sharp';
2
+ /**
3
+ * Analyze a frame image to detect the transparent/screen area
4
+ * This helps us automatically determine where the screenshot should be placed
5
+ */
6
+ export async function analyzeFrameScreenArea(framePath) {
7
+ try {
8
+ const image = sharp(framePath);
9
+ const metadata = await image.metadata();
10
+ if (!metadata.width || !metadata.height) {
11
+ return null;
12
+ }
13
+ // Get raw pixel data
14
+ const { data, info } = await image
15
+ .raw()
16
+ .toBuffer({ resolveWithObject: true });
17
+ const width = info.width;
18
+ const height = info.height;
19
+ const channels = info.channels;
20
+ // Find the bounds of the transparent/white area
21
+ let minX = width;
22
+ let minY = height;
23
+ let maxX = 0;
24
+ let maxY = 0;
25
+ // Sample the image to find transparent or near-white pixels
26
+ // We'll check every 10th pixel for performance
27
+ const step = 10;
28
+ for (let y = 0; y < height; y += step) {
29
+ for (let x = 0; x < width; x += step) {
30
+ const idx = (y * width + x) * channels;
31
+ if (channels === 4) {
32
+ // RGBA - check for transparency
33
+ const alpha = data[idx + 3];
34
+ if (alpha < 128) { // Mostly transparent
35
+ minX = Math.min(minX, x);
36
+ minY = Math.min(minY, y);
37
+ maxX = Math.max(maxX, x);
38
+ maxY = Math.max(maxY, y);
39
+ }
40
+ }
41
+ else if (channels === 3) {
42
+ // RGB - check for white/light gray (common screen placeholder color)
43
+ const r = data[idx];
44
+ const g = data[idx + 1];
45
+ const b = data[idx + 2];
46
+ // Check if it's near white
47
+ if (r > 240 && g > 240 && b > 240) {
48
+ minX = Math.min(minX, x);
49
+ minY = Math.min(minY, y);
50
+ maxX = Math.max(maxX, x);
51
+ maxY = Math.max(maxY, y);
52
+ }
53
+ }
54
+ }
55
+ }
56
+ // Validate we found a reasonable screen area
57
+ if (minX >= maxX || minY >= maxY) {
58
+ return null;
59
+ }
60
+ // Refine the bounds by checking edges more precisely
61
+ // This is important for accurate placement
62
+ return {
63
+ x: minX,
64
+ y: minY,
65
+ width: maxX - minX,
66
+ height: maxY - minY
67
+ };
68
+ }
69
+ catch (error) {
70
+ console.error('Error analyzing frame:', error);
71
+ return null;
72
+ }
73
+ }
74
+ /**
75
+ * Get screen area from Frames.json data or analyze the frame
76
+ */
77
+ export async function getScreenArea(framePath, frameData) {
78
+ // First try to analyze the actual frame image
79
+ const analyzed = await analyzeFrameScreenArea(framePath);
80
+ if (analyzed) {
81
+ return analyzed;
82
+ }
83
+ // Fallback to using the x,y offsets from Frames.json
84
+ // These represent the padding/margins
85
+ const frameMetadata = await sharp(framePath).metadata();
86
+ const frameWidth = frameMetadata.width || 0;
87
+ const frameHeight = frameMetadata.height || 0;
88
+ const x = parseInt(frameData.x || '0');
89
+ const y = parseInt(frameData.y || '0');
90
+ return {
91
+ x,
92
+ y,
93
+ width: frameWidth - x * 2,
94
+ height: frameHeight - y * 2
95
+ };
96
+ }
97
+ //# sourceMappingURL=frames-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frames-analyzer.js","sourceRoot":"","sources":["../../src/core/frames-analyzer.ts"],"names":[],"mappings":"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"}