appshot-cli 0.8.5 → 0.8.7

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.
package/README.md CHANGED
@@ -8,9 +8,9 @@
8
8
  [![Node.js Version](https://img.shields.io/node/v/appshot-cli.svg)](https://nodejs.org)
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
10
 
11
- šŸ†• **Version 0.8.5** - **Watch Mode & Device Capture** - File system monitoring with auto-processing and direct iOS simulator capture!
11
+ šŸ†• **Version 0.8.6** - **Background Images** - Replace gradients with custom static images, auto-detection, and dimension validation!
12
12
 
13
- > āš ļø **NEW in v0.8.5**: `appshot watch` for automatic screenshot processing and `appshot device` for iOS simulator capture (macOS only). Includes duplicate detection and background service management.
13
+ > āš ļø **NEW in v0.8.6**: Use custom background images instead of gradients! Auto-detects `background.png` in device folders, supports multiple fit modes, and validates dimensions against App Store specs.
14
14
 
15
15
  > āš ļø **BREAKING CHANGE in v0.4.0**: Output structure now always uses language subdirectories.
16
16
  > Single language builds now output to `final/device/lang/` instead of `final/device/`.
@@ -170,7 +170,7 @@ final/
170
170
 
171
171
  ### Project Structure
172
172
 
173
- Appshot uses a simple, predictable directory structure:
173
+ Appshot creates and manages the following directory structure in your project:
174
174
 
175
175
  ```
176
176
  your-project/
@@ -178,16 +178,38 @@ your-project/
178
178
  │ ā”œā”€ā”€ config.json # Main configuration
179
179
  │ ā”œā”€ā”€ captions/ # Device-specific captions
180
180
  │ │ ā”œā”€ā”€ iphone.json
181
- │ │ └── ipad.json
182
- │ └── caption-history.json # Autocomplete history
183
- ā”œā”€ā”€ screenshots/ # Original screenshots
181
+ │ │ ā”œā”€ā”€ ipad.json
182
+ │ │ ā”œā”€ā”€ mac.json
183
+ │ │ └── watch.json
184
+ │ ā”œā”€ā”€ caption-history.json # Autocomplete history (created on use)
185
+ │ ā”œā”€ā”€ ai-config.json # AI translation settings (optional)
186
+ │ ā”œā”€ā”€ processed/ # Watch mode tracking (macOS only)
187
+ │ └── watch.pid # Watch service PID (macOS only)
188
+ ā”œā”€ā”€ screenshots/ # Your original screenshots
184
189
  │ ā”œā”€ā”€ iphone/
190
+ │ │ └── background.png # Optional device background
185
191
  │ ā”œā”€ā”€ ipad/
186
- │ └── mac/
187
- ā”œā”€ā”€ frames/ # Device frames (auto-downloaded)
192
+ │ ā”œā”€ā”€ mac/
193
+ │ └── watch/
188
194
  └── final/ # Generated output
195
+ ā”œā”€ā”€ iphone/
196
+ │ ā”œā”€ā”€ en/ # Language subdirectory (always created)
197
+ │ ā”œā”€ā”€ es/ # Additional languages as needed
198
+ │ └── fr/
199
+ └── ipad/
200
+ └── en/
189
201
  ```
190
202
 
203
+ **Created by `appshot init`:**
204
+ - `.appshot/` directory with config.json
205
+ - `.appshot/captions/` with device JSON files
206
+ - `screenshots/` directories for each device type
207
+
208
+ **Created during usage:**
209
+ - `final/` - Created when you run `appshot build`
210
+ - `.appshot/caption-history.json` - Created when using captions
211
+ - `.appshot/processed/` - Created by watch mode (macOS)
212
+
191
213
  ### Configuration Overview
192
214
 
193
215
  All settings are stored in `.appshot/config.json`:
@@ -207,7 +229,7 @@ All settings are stored in `.appshot/config.json`:
207
229
  },
208
230
  "devices": {
209
231
  "iphone": {
210
- "resolution": "1284x2778",
232
+ "resolution": "1290x2796",
211
233
  "autoFrame": true
212
234
  }
213
235
  }
@@ -267,6 +289,129 @@ appshot gradients --sample
267
289
  }
268
290
  ```
269
291
 
292
+ ### Background System (NEW in v0.8.6)
293
+
294
+ Replace gradients with custom static background images for a unique, branded look. Appshot supports automatic detection, multiple formats, and intelligent scaling.
295
+
296
+ #### Background Locations
297
+
298
+ Backgrounds are searched in priority order:
299
+
300
+ 1. **Device-specific**: `screenshots/<device>/background.png`
301
+ 2. **Global**: `screenshots/background.png`
302
+ 3. **Custom**: Path specified via config or CLI
303
+
304
+ #### Background Commands
305
+
306
+ ```bash
307
+ # Set background for a device
308
+ appshot backgrounds set iphone ./backgrounds/sunset.jpg
309
+
310
+ # Set global background for all devices
311
+ appshot backgrounds set --global ./backgrounds/brand-bg.png
312
+
313
+ # Validate dimensions against App Store specs
314
+ appshot backgrounds validate
315
+
316
+ # List all configured backgrounds
317
+ appshot backgrounds list
318
+
319
+ # Clear background configuration
320
+ appshot backgrounds clear iphone
321
+ ```
322
+
323
+ #### Build Options
324
+
325
+ ```bash
326
+ # Auto-detect background.png in device folders
327
+ appshot build --auto-background
328
+
329
+ # Use specific background image
330
+ appshot build --background ./assets/custom-bg.png
331
+
332
+ # Set background fit mode
333
+ appshot build --background-fit cover
334
+
335
+ # Disable backgrounds (transparent)
336
+ appshot build --no-background
337
+ ```
338
+
339
+ #### Fit Modes
340
+
341
+ - **`cover`** - Scale to cover entire area (may crop)
342
+ - **`contain`** - Scale to fit within area (may add letterbox bars)
343
+ - **`fill`** - Stretch to exact dimensions (may distort)
344
+ - **`scale-down`** - Only scale down if larger, never scale up
345
+
346
+ #### Creating Backgrounds with ImageMagick
347
+
348
+ ImageMagick is a powerful CLI tool for creating custom backgrounds:
349
+
350
+ ```bash
351
+ # Solid color background
352
+ magick -size 1290x2796 canvas:navy background.png
353
+
354
+ # Gradient background
355
+ magick -size 1290x2796 gradient:blue-purple background.png
356
+
357
+ # Radial gradient
358
+ magick -size 1290x2796 radial-gradient:white-darkblue background.png
359
+
360
+ # Plasma fractal pattern
361
+ magick -size 1290x2796 plasma:fractal background.png
362
+
363
+ # Blurred noise texture
364
+ magick -size 1290x2796 xc: +noise Random -blur 0x10 background.png
365
+
366
+ # Tiled pattern
367
+ magick -size 100x100 pattern:checkerboard -scale 1290x2796 background.png
368
+
369
+ # Multi-point color interpolation
370
+ magick -size 1290x2796 xc: -sparse-color barycentric \
371
+ '0,0 skyblue 1290,0 white 645,2796 lightblue' background.png
372
+ ```
373
+
374
+ #### Configuration
375
+
376
+ ```json
377
+ {
378
+ "background": {
379
+ "mode": "image",
380
+ "image": "./backgrounds/global.png",
381
+ "fit": "cover"
382
+ },
383
+ "devices": {
384
+ "iphone": {
385
+ "background": {
386
+ "image": "./backgrounds/iphone.png",
387
+ "fit": "contain"
388
+ }
389
+ }
390
+ }
391
+ }
392
+ ```
393
+
394
+ #### Mixed Configurations
395
+
396
+ You can mix backgrounds and gradients across devices:
397
+
398
+ - iPhone uses a custom background image
399
+ - iPad falls back to gradient
400
+ - Mac uses a different background
401
+ - Watch uses the global background
402
+
403
+ This flexibility allows you to optimize each device's appearance independently.
404
+
405
+ #### Dimension Validation
406
+
407
+ Appshot validates background dimensions and warns about:
408
+
409
+ - Images smaller than target resolution (will be upscaled)
410
+ - Aspect ratio mismatches (may cause cropping/distortion)
411
+ - Large file sizes (>10MB triggers optimization suggestion)
412
+
413
+ Use `appshot backgrounds validate` to check all backgrounds before building.
414
+
270
415
  ### Font System
271
416
 
272
417
  Version 0.4.0 introduces comprehensive font management with intelligent fallbacks.
@@ -1345,7 +1490,7 @@ appshot validate [options]
1345
1490
  "devices": {
1346
1491
  "iphone": {
1347
1492
  "input": "./screenshots/iphone",
1348
- "resolution": "1284x2778",
1493
+ "resolution": "1290x2796",
1349
1494
  "autoFrame": true,
1350
1495
  "preferredFrame": "frame-name",
1351
1496
  "partialFrame": false,
@@ -1375,7 +1520,7 @@ Each device can override global settings:
1375
1520
  "iphone": {
1376
1521
  // Required
1377
1522
  "input": "./screenshots/iphone",
1378
- "resolution": "1284x2778",
1523
+ "resolution": "1290x2796",
1379
1524
 
1380
1525
  // Frame options
1381
1526
  "autoFrame": true,
@@ -1531,7 +1676,7 @@ cat > .appshot/config.json << EOF
1531
1676
  {
1532
1677
  "gradient": {"colors": ["#FF5733", "#FFC300"]},
1533
1678
  "devices": {
1534
- "iphone": {"resolution": "1284x2778"}
1679
+ "iphone": {"resolution": "1290x2796"}
1535
1680
  }
1536
1681
  }
1537
1682
  EOF
@@ -1569,7 +1714,7 @@ def generate_screenshots(device, captions):
1569
1714
  config = {
1570
1715
  "gradient": {"colors": ["#0077BE", "#33CCCC"]},
1571
1716
  "devices": {
1572
- device: {"resolution": "1284x2778"}
1717
+ device: {"resolution": "1290x2796"}
1573
1718
  }
1574
1719
  }
1575
1720
 
@@ -1679,7 +1824,7 @@ For identical device positioning across all screenshots:
1679
1824
  {
1680
1825
  "devices": {
1681
1826
  "iphone": {
1682
- "resolution": "1284x2778",
1827
+ "resolution": "1290x2796",
1683
1828
  "autoFrame": false,
1684
1829
  "preferredFrame": "iphone-16-pro-max-portrait",
1685
1830
  "frameScale": 0.85,
@@ -1927,12 +2072,25 @@ appshot/
1927
2072
  ā”œā”€ā”€ src/
1928
2073
  │ ā”œā”€ā”€ cli.ts # Entry point
1929
2074
  │ ā”œā”€ā”€ commands/ # Command implementations
1930
- │ ā”œā”€ā”€ core/ # Core functionality
1931
- │ ā”œā”€ā”€ services/ # Services (fonts, translation)
1932
- │ └── types.ts # TypeScript definitions
1933
- ā”œā”€ā”€ tests/ # Test files
1934
- ā”œā”€ā”€ frames/ # Device frame images
1935
- └── examples/ # Example projects
2075
+ │ ā”œā”€ā”€ core/ # Core functionality
2076
+ │ ā”œā”€ā”€ services/ # Services (fonts, translation, etc)
2077
+ │ ā”œā”€ā”€ types/ # TypeScript type definitions
2078
+ │ ā”œā”€ā”€ utils/ # Utility functions
2079
+ │ └── types.ts # Main type definitions
2080
+ ā”œā”€ā”€ tests/ # Test files
2081
+ │ ā”œā”€ā”€ integration/ # Integration tests
2082
+ │ └── visual/ # Visual regression tests
2083
+ ā”œā”€ā”€ fonts/ # Embedded font files
2084
+ │ ā”œā”€ā”€ Inter/
2085
+ │ ā”œā”€ā”€ Poppins/
2086
+ │ ā”œā”€ā”€ Montserrat/
2087
+ │ └── ... # 10+ font families
2088
+ ā”œā”€ā”€ frames/ # Device frame images
2089
+ ā”œā”€ā”€ assets/ # Static assets
2090
+ │ ā”œā”€ā”€ specs/ # App Store specifications
2091
+ │ └── frames/ # Frame metadata
2092
+ └── examples/ # Example projects
2093
+ └── minimal-project/ # Basic example setup
1936
2094
  ```
1937
2095
 
1938
2096
  ### Testing
@@ -2044,7 +2202,7 @@ For security vulnerabilities, please see [SECURITY.md](SECURITY.md).
2044
2202
  ### NPM Package
2045
2203
 
2046
2204
  - šŸ“¦ [appshot-cli on NPM](https://www.npmjs.com/package/appshot-cli)
2047
- - šŸ”„ Latest version: 0.8.5
2205
+ - šŸ”„ Latest version: 0.8.6
2048
2206
 
2049
2207
  ---
2050
2208
 
package/dist/cli.js CHANGED
@@ -12,6 +12,7 @@ import presetsCmd from './commands/presets.js';
12
12
  import validateCmd from './commands/validate.js';
13
13
  import styleCmd from './commands/style.js';
14
14
  import gradientsCmd from './commands/gradients.js';
15
+ import backgroundsCmd from './commands/backgrounds.js';
15
16
  import fontsCmd from './commands/fonts.js';
16
17
  import migrateCmd from './commands/migrate.js';
17
18
  import { createCleanCommand } from './commands/clean.js';
@@ -23,12 +24,12 @@ import watchStatusCmd from './commands/watch-status.js';
23
24
  const program = new Command();
24
25
  program
25
26
  .name('appshot')
26
- .description(`Generate App Store–ready screenshots with frames, gradients, and captions.
27
+ .description(`Generate App Store–ready screenshots with frames, backgrounds, and captions.
27
28
 
28
29
  ${pc.bold('Features:')}
29
30
  • Auto-detects portrait/landscape orientation
30
31
  • 8 embedded font families with italic & bold variants
31
- • 24+ gradient presets with visual preview
32
+ • Custom background images or 24+ gradient presets
32
33
  • AI-powered translation to 25+ languages
33
34
  • Smart caption wrapping and positioning
34
35
  • All official App Store resolutions
@@ -44,6 +45,7 @@ ${pc.bold('Quick Start:')}
44
45
  ${pc.bold('Common Workflows:')}
45
46
  $ appshot fonts --set "Poppins Italic" # Set italic font
46
47
  $ appshot gradients select # Pick gradient
48
+ $ appshot backgrounds set iphone bg.jpg # Set background image
47
49
  $ appshot frame ./screenshots --recursive # Batch frame images
48
50
  $ appshot build --preset iphone-6-9,ipad-13 # App Store presets
49
51
  $ appshot localize --langs es,fr,de # Batch translate${platform() === 'darwin' ? `
@@ -51,7 +53,7 @@ ${pc.bold('Common Workflows:')}
51
53
  $ appshot watch start --process # Auto-process new screenshots` : ''}
52
54
 
53
55
  ${pc.dim('Docs: https://github.com/chrisvanbuskirk/appshot')}`)
54
- .version('0.8.5')
56
+ .version('0.8.7')
55
57
  .addHelpText('after', `\n${pc.bold('Environment Variables:')}
56
58
  OPENAI_API_KEY API key for translation features
57
59
  APPSHOT_DISABLE_FONT_SCAN Skip system font detection (CI optimization)
@@ -66,6 +68,7 @@ program.addCommand(initCmd());
66
68
  program.addCommand(captionCmd());
67
69
  program.addCommand(styleCmd());
68
70
  program.addCommand(gradientsCmd());
71
+ program.addCommand(backgroundsCmd());
69
72
  program.addCommand(fontsCmd());
70
73
  program.addCommand(localizeCmd());
71
74
  program.addCommand(buildCmd());
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAC9B,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,OAAO,MAAM,oBAAoB,CAAC;AACzC,OAAO,UAAU,MAAM,uBAAuB,CAAC;AAC/C,OAAO,WAAW,MAAM,wBAAwB,CAAC;AACjD,OAAO,QAAQ,MAAM,qBAAqB,CAAC;AAC3C,OAAO,QAAQ,MAAM,qBAAqB,CAAC;AAC3C,OAAO,QAAQ,MAAM,qBAAqB,CAAC;AAC3C,OAAO,SAAS,MAAM,sBAAsB,CAAC;AAC7C,OAAO,UAAU,MAAM,uBAAuB,CAAC;AAC/C,OAAO,WAAW,MAAM,wBAAwB,CAAC;AACjD,OAAO,QAAQ,MAAM,qBAAqB,CAAC;AAC3C,OAAO,YAAY,MAAM,yBAAyB,CAAC;AACnD,OAAO,cAAc,MAAM,2BAA2B,CAAC;AACvD,OAAO,QAAQ,MAAM,qBAAqB,CAAC;AAC3C,OAAO,UAAU,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,QAAQ,MAAM,qBAAqB,CAAC;AAC3C,OAAO,SAAS,MAAM,sBAAsB,CAAC;AAC7C,OAAO,QAAQ,MAAM,qBAAqB,CAAC;AAC3C,OAAO,UAAU,MAAM,uBAAuB,CAAC;AAC/C,OAAO,cAAc,MAAM,4BAA4B,CAAC;AAExD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC;;EAEb,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC;;;;;;;;;;EAUpB,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC;;;;;;EAMvB,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC;;;;;;gEAMkC,QAAQ,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC;;6EAEb,CAAC,CAAC,CAAC,EAAE;;EAEhF,EAAE,CAAC,GAAG,CAAC,kDAAkD,CAAC,EAAE,CAAC;KAC5D,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,wBAAwB,CAAC;;;;EAI5D,EAAE,CAAC,IAAI,CAAC,sBAAsB,CAAC;;;;;EAK/B,EAAE,CAAC,GAAG,CAAC,uDAAuD,CAAC,EAAE,CAAC,CAAC;AAErE,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;AAC9B,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;AAC/B,OAAO,CAAC,UAAU,CAAC,YAAY,EAAE,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC,CAAC;AACrC,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;AAC/B,OAAO,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;AAC/B,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;AAE/B,8CAA8C;AAC9C,IAAI,QAAQ,EAAE,KAAK,QAAQ,EAAE,CAAC;IAC5B,OAAO,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,CAAC;IAChC,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC/B,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC;IACjC,OAAO,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC,CAAC;AACvC,CAAC;AAED,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;AAC/B,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;AAC/B,OAAO,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAAC;AAEzC,OAAO,CAAC,kBAAkB,CAAC,EAAE,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,CAAC;AAE9D,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACjC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export default function backgroundsCommand(): Command;
3
+ //# sourceMappingURL=backgrounds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backgrounds.d.ts","sourceRoot":"","sources":["../../src/commands/backgrounds.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,MAAM,CAAC,OAAO,UAAU,kBAAkB,IAAI,OAAO,CAmWpD"}
@@ -0,0 +1,323 @@
1
+ import { Command } from 'commander';
2
+ import { promises as fs } from 'fs';
3
+ import path from 'path';
4
+ import pc from 'picocolors';
5
+ import { select, input } from '@inquirer/prompts';
6
+ import { validateBackgroundDimensions, detectBestFit } from '../core/background.js';
7
+ import { loadConfig, saveConfig } from '../core/files.js';
8
+ export default function backgroundsCommand() {
9
+ const cmd = new Command('backgrounds')
10
+ .description('Manage background images for screenshots')
11
+ .addHelpText('after', `
12
+ ${pc.bold('Examples:')}
13
+ $ appshot backgrounds set iphone ./backgrounds/sunset.jpg
14
+ $ appshot backgrounds validate
15
+ $ appshot backgrounds preview
16
+ $ appshot backgrounds clear iphone
17
+ $ appshot backgrounds list
18
+
19
+ ${pc.bold('Background Locations:')}
20
+ Device-specific: screenshots/<device>/background.png
21
+ Global: screenshots/background.png
22
+ Custom: Specified via config or command
23
+
24
+ ${pc.bold('Fit Modes:')}
25
+ cover Scale to cover entire area (may crop)
26
+ contain Scale to fit within area (may add bars)
27
+ fill Stretch to exact dimensions (may distort)
28
+ scale-down Only scale down if larger, never scale up
29
+ `);
30
+ // Set background for a device
31
+ cmd
32
+ .command('set')
33
+ .description('Set background image for a device')
34
+ .argument('[device]', 'Device type (iphone, ipad, mac, watch)')
35
+ .argument('[image]', 'Path to background image')
36
+ .option('-f, --fit <mode>', 'Fit mode: cover, contain, fill, scale-down', 'cover')
37
+ .option('--global', 'Set as global background for all devices')
38
+ .action(async (device, image, options) => {
39
+ try {
40
+ // Interactive mode if arguments not provided
41
+ if (!device && !options.global) {
42
+ device = await select({
43
+ message: 'Select device type:',
44
+ choices: [
45
+ { name: 'iPhone', value: 'iphone' },
46
+ { name: 'iPad', value: 'ipad' },
47
+ { name: 'Mac', value: 'mac' },
48
+ { name: 'Watch', value: 'watch' },
49
+ { name: 'All Devices (Global)', value: 'global' }
50
+ ]
51
+ });
52
+ if (device === 'global') {
53
+ options.global = true;
54
+ device = undefined;
55
+ }
56
+ }
57
+ if (!image) {
58
+ image = await input({
59
+ message: 'Enter path to background image:',
60
+ validate: async (value) => {
61
+ try {
62
+ await fs.access(value);
63
+ return true;
64
+ }
65
+ catch {
66
+ return 'File not found';
67
+ }
68
+ }
69
+ });
70
+ }
71
+ // Validate image exists
72
+ try {
73
+ await fs.access(image);
74
+ }
75
+ catch {
76
+ console.error(pc.red(`āŒ Background image not found: ${image}`));
77
+ process.exit(1);
78
+ }
79
+ // Load config
80
+ const config = await loadConfig();
81
+ // Initialize background config if not exists
82
+ if (!config.background) {
83
+ config.background = {
84
+ mode: 'image',
85
+ warnOnMismatch: true
86
+ };
87
+ }
88
+ // Set background
89
+ if (options.global) {
90
+ config.background.image = image;
91
+ config.background.fit = options.fit;
92
+ console.log(pc.green(`āœ… Set global background: ${image}`));
93
+ }
94
+ else {
95
+ // Device-specific background
96
+ if (!config.devices[device]) {
97
+ console.error(pc.red(`āŒ Device '${device}' not found in config`));
98
+ console.log(pc.dim(`Available devices: ${Object.keys(config.devices).join(', ')}`));
99
+ process.exit(1);
100
+ }
101
+ if (!config.devices[device].background) {
102
+ config.devices[device].background = {};
103
+ }
104
+ config.devices[device].background.image = image;
105
+ config.devices[device].background.fit = options.fit;
106
+ console.log(pc.green(`āœ… Set ${device} background: ${image}`));
107
+ }
108
+ // Save config
109
+ await saveConfig(config);
110
+ console.log(pc.dim('Configuration saved'));
111
+ }
112
+ catch (error) {
113
+ console.error(pc.red('Error setting background:'), error);
114
+ process.exit(1);
115
+ }
116
+ });
117
+ // Validate backgrounds
118
+ cmd
119
+ .command('validate')
120
+ .description('Validate background dimensions against App Store specs')
121
+ .option('-d, --device <type>', 'Validate specific device only')
122
+ .action(async (options) => {
123
+ try {
124
+ const config = await loadConfig();
125
+ let hasWarnings = false;
126
+ // Get devices to validate
127
+ const devices = options.device
128
+ ? [options.device]
129
+ : Object.keys(config.devices);
130
+ console.log(pc.bold('\nšŸ“ Validating background dimensions...\n'));
131
+ for (const device of devices) {
132
+ const deviceConfig = config.devices[device];
133
+ if (!deviceConfig)
134
+ continue;
135
+ // Find background image
136
+ let backgroundPath = null;
137
+ if (deviceConfig.background?.image) {
138
+ backgroundPath = deviceConfig.background.image;
139
+ }
140
+ else if (config.background?.image) {
141
+ backgroundPath = config.background.image;
142
+ }
143
+ else {
144
+ // Check for auto-detected background
145
+ const candidates = [
146
+ path.join(deviceConfig.input, 'background.png'),
147
+ path.join(deviceConfig.input, 'background.jpg'),
148
+ path.join('screenshots', 'background.png'),
149
+ path.join('screenshots', 'background.jpg')
150
+ ];
151
+ for (const candidate of candidates) {
152
+ try {
153
+ await fs.access(candidate);
154
+ backgroundPath = candidate;
155
+ break;
156
+ }
157
+ catch {
158
+ // Continue checking
159
+ }
160
+ }
161
+ }
162
+ if (!backgroundPath) {
163
+ console.log(pc.dim(`${device}: No background image found`));
164
+ continue;
165
+ }
166
+ // Get target dimensions
167
+ const [width, height] = deviceConfig.resolution.split('x').map(Number);
168
+ // Validate
169
+ const validation = await validateBackgroundDimensions(backgroundPath, width, height);
170
+ // Display results
171
+ console.log(pc.cyan(`${device}:`));
172
+ console.log(pc.dim(` Background: ${backgroundPath}`));
173
+ console.log(pc.dim(` Source: ${validation.dimensions.source.width}x${validation.dimensions.source.height}`));
174
+ console.log(pc.dim(` Target: ${validation.dimensions.target.width}x${validation.dimensions.target.height}`));
175
+ if (validation.warnings.length > 0) {
176
+ hasWarnings = true;
177
+ validation.warnings.forEach(warning => {
178
+ console.log(pc.yellow(` āš ļø ${warning}`));
179
+ });
180
+ // Suggest best fit mode
181
+ const bestFit = detectBestFit(validation.dimensions.source.width, validation.dimensions.source.height, validation.dimensions.target.width, validation.dimensions.target.height);
182
+ console.log(pc.cyan(` šŸ’” Suggested fit mode: ${bestFit}`));
183
+ }
184
+ else {
185
+ console.log(pc.green(' āœ… Dimensions OK'));
186
+ }
187
+ console.log();
188
+ }
189
+ if (hasWarnings) {
190
+ console.log(pc.yellow('āš ļø Some backgrounds have dimension warnings'));
191
+ console.log(pc.dim('Run "appshot backgrounds set" to adjust fit modes'));
192
+ }
193
+ else {
194
+ console.log(pc.green('āœ… All backgrounds validated successfully'));
195
+ }
196
+ }
197
+ catch (error) {
198
+ console.error(pc.red('Error validating backgrounds:'), error);
199
+ process.exit(1);
200
+ }
201
+ });
202
+ // Preview backgrounds
203
+ cmd
204
+ .command('preview')
205
+ .description('Generate preview of screenshots with backgrounds')
206
+ .option('-d, --device <type>', 'Preview specific device only')
207
+ .option('-o, --output <dir>', 'Output directory', './preview')
208
+ .action(async (options) => {
209
+ try {
210
+ const config = await loadConfig();
211
+ const outputDir = options.output;
212
+ // Create output directory
213
+ await fs.mkdir(outputDir, { recursive: true });
214
+ console.log(pc.bold('\nšŸŽØ Generating background previews...\n'));
215
+ // This would integrate with the compose system
216
+ // For now, just show what would be generated
217
+ const devices = options.device
218
+ ? [options.device]
219
+ : Object.keys(config.devices);
220
+ for (const device of devices) {
221
+ console.log(pc.cyan(`${device}:`));
222
+ console.log(pc.dim(` Would generate preview in ${outputDir}/${device}/`));
223
+ }
224
+ console.log(pc.dim('\nNote: Full preview generation requires running "appshot build --preview"'));
225
+ }
226
+ catch (error) {
227
+ console.error(pc.red('Error generating preview:'), error);
228
+ process.exit(1);
229
+ }
230
+ });
231
+ // Clear background
232
+ cmd
233
+ .command('clear')
234
+ .description('Remove background configuration')
235
+ .argument('[device]', 'Device type to clear (or "all" for global)')
236
+ .action(async (device) => {
237
+ try {
238
+ if (!device) {
239
+ device = await select({
240
+ message: 'Clear background for:',
241
+ choices: [
242
+ { name: 'iPhone', value: 'iphone' },
243
+ { name: 'iPad', value: 'ipad' },
244
+ { name: 'Mac', value: 'mac' },
245
+ { name: 'Watch', value: 'watch' },
246
+ { name: 'All Devices (Global)', value: 'all' }
247
+ ]
248
+ });
249
+ }
250
+ const config = await loadConfig();
251
+ if (device === 'all') {
252
+ // Clear global background
253
+ if (config.background) {
254
+ delete config.background.image;
255
+ console.log(pc.green('āœ… Cleared global background'));
256
+ }
257
+ }
258
+ else {
259
+ // Clear device-specific background
260
+ if (config.devices[device]?.background) {
261
+ delete config.devices[device].background.image;
262
+ console.log(pc.green(`āœ… Cleared ${device} background`));
263
+ }
264
+ }
265
+ await saveConfig(config);
266
+ console.log(pc.dim('Configuration saved'));
267
+ }
268
+ catch (error) {
269
+ console.error(pc.red('Error clearing background:'), error);
270
+ process.exit(1);
271
+ }
272
+ });
273
+ // List backgrounds
274
+ cmd
275
+ .command('list')
276
+ .description('List configured backgrounds')
277
+ .action(async () => {
278
+ try {
279
+ const config = await loadConfig();
280
+ console.log(pc.bold('\nšŸ“‹ Configured Backgrounds:\n'));
281
+ // Global background
282
+ if (config.background?.image) {
283
+ console.log(pc.cyan('Global:'));
284
+ console.log(pc.dim(` Image: ${config.background.image}`));
285
+ console.log(pc.dim(` Fit: ${config.background.fit || 'cover'}`));
286
+ console.log();
287
+ }
288
+ // Device-specific backgrounds
289
+ for (const [device, deviceConfig] of Object.entries(config.devices)) {
290
+ if (deviceConfig.background?.image) {
291
+ console.log(pc.cyan(`${device}:`));
292
+ console.log(pc.dim(` Image: ${deviceConfig.background.image}`));
293
+ console.log(pc.dim(` Fit: ${deviceConfig.background.fit || 'cover'}`));
294
+ console.log();
295
+ }
296
+ }
297
+ // Auto-detected backgrounds
298
+ console.log(pc.bold('Auto-detected backgrounds:'));
299
+ for (const [device, deviceConfig] of Object.entries(config.devices)) {
300
+ const candidates = [
301
+ path.join(deviceConfig.input, 'background.png'),
302
+ path.join(deviceConfig.input, 'background.jpg')
303
+ ];
304
+ for (const candidate of candidates) {
305
+ try {
306
+ await fs.access(candidate);
307
+ console.log(pc.dim(` ${device}: ${candidate}`));
308
+ break;
309
+ }
310
+ catch {
311
+ // Not found
312
+ }
313
+ }
314
+ }
315
+ }
316
+ catch (error) {
317
+ console.error(pc.red('Error listing backgrounds:'), error);
318
+ process.exit(1);
319
+ }
320
+ });
321
+ return cmd;
322
+ }
323
+ //# sourceMappingURL=backgrounds.js.map