appshot-cli 0.5.0 → 0.7.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 (83) hide show
  1. package/README.md +185 -14
  2. package/dist/cli.js +34 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/build.d.ts.map +1 -1
  5. package/dist/commands/build.js +145 -62
  6. package/dist/commands/build.js.map +1 -1
  7. package/dist/commands/caption.d.ts.map +1 -1
  8. package/dist/commands/caption.js +28 -1
  9. package/dist/commands/caption.js.map +1 -1
  10. package/dist/commands/fonts.d.ts.map +1 -1
  11. package/dist/commands/fonts.js +89 -6
  12. package/dist/commands/fonts.js.map +1 -1
  13. package/dist/commands/gradients.d.ts.map +1 -1
  14. package/dist/commands/gradients.js +42 -2
  15. package/dist/commands/gradients.js.map +1 -1
  16. package/dist/commands/init.d.ts.map +1 -1
  17. package/dist/commands/init.js +20 -1
  18. package/dist/commands/init.js.map +1 -1
  19. package/dist/commands/localize.d.ts.map +1 -1
  20. package/dist/commands/localize.js +33 -1
  21. package/dist/commands/localize.js.map +1 -1
  22. package/dist/commands/style.d.ts.map +1 -1
  23. package/dist/commands/style.js +156 -2
  24. package/dist/commands/style.js.map +1 -1
  25. package/dist/core/compose.d.ts +6 -0
  26. package/dist/core/compose.d.ts.map +1 -1
  27. package/dist/core/compose.js +433 -47
  28. package/dist/core/compose.js.map +1 -1
  29. package/dist/core/devices.d.ts +1 -1
  30. package/dist/core/devices.d.ts.map +1 -1
  31. package/dist/core/devices.js +5 -1
  32. package/dist/core/devices.js.map +1 -1
  33. package/dist/core/files.d.ts +1 -1
  34. package/dist/core/files.d.ts.map +1 -1
  35. package/dist/core/files.js +15 -3
  36. package/dist/core/files.js.map +1 -1
  37. package/dist/services/doctor.js +1 -1
  38. package/dist/services/fonts.d.ts +37 -2
  39. package/dist/services/fonts.d.ts.map +1 -1
  40. package/dist/services/fonts.js +225 -0
  41. package/dist/services/fonts.js.map +1 -1
  42. package/dist/types.d.ts +15 -2
  43. package/dist/types.d.ts.map +1 -1
  44. package/fonts/DMSans/DMSans-LICENSE.txt +93 -0
  45. package/fonts/DMSans/DMSans-Regular.ttf +0 -0
  46. package/fonts/FiraCode/FiraCode-Bold.ttf +0 -0
  47. package/fonts/FiraCode/FiraCode-LICENSE.txt +93 -0
  48. package/fonts/FiraCode/FiraCode-Light.ttf +0 -0
  49. package/fonts/FiraCode/FiraCode-Regular.ttf +0 -0
  50. package/fonts/FiraCode/FiraCode-SemiBold.ttf +0 -0
  51. package/fonts/Inter/InterVariable-Italic.ttf +0 -0
  52. package/fonts/Inter/InterVariable.ttf +0 -0
  53. package/fonts/Inter/LICENSE.txt +92 -0
  54. package/fonts/JetBrainsMono/JetBrainsMono-Bold.ttf +0 -0
  55. package/fonts/JetBrainsMono/JetBrainsMono-BoldItalic.ttf +0 -0
  56. package/fonts/JetBrainsMono/JetBrainsMono-Italic.ttf +0 -0
  57. package/fonts/JetBrainsMono/JetBrainsMono-LICENSE.txt +93 -0
  58. package/fonts/JetBrainsMono/JetBrainsMono-Regular.ttf +0 -0
  59. package/fonts/Lato/Lato-Bold.ttf +0 -0
  60. package/fonts/Lato/Lato-BoldItalic.ttf +0 -0
  61. package/fonts/Lato/Lato-Italic.ttf +0 -0
  62. package/fonts/Lato/Lato-LICENSE.txt +93 -0
  63. package/fonts/Lato/Lato-Regular.ttf +0 -0
  64. package/fonts/Montserrat/Montserrat-Bold.ttf +2070 -0
  65. package/fonts/Montserrat/Montserrat-BoldItalic.ttf +2070 -0
  66. package/fonts/Montserrat/Montserrat-Italic.ttf +2070 -0
  67. package/fonts/Montserrat/Montserrat-LICENSE.txt +93 -0
  68. package/fonts/Montserrat/Montserrat-Regular.ttf +2070 -0
  69. package/fonts/OpenSans/OpenSans-LICENSE.txt +92 -0
  70. package/fonts/OpenSans/OpenSans-Regular.ttf +0 -0
  71. package/fonts/Poppins/Poppins-Bold.ttf +0 -0
  72. package/fonts/Poppins/Poppins-BoldItalic.ttf +0 -0
  73. package/fonts/Poppins/Poppins-Italic.ttf +0 -0
  74. package/fonts/Poppins/Poppins-LICENSE.txt +93 -0
  75. package/fonts/Poppins/Poppins-Regular.ttf +0 -0
  76. package/fonts/Roboto/Roboto-Bold.ttf +2070 -0
  77. package/fonts/Roboto/Roboto-BoldItalic.ttf +2070 -0
  78. package/fonts/Roboto/Roboto-Italic.ttf +2070 -0
  79. package/fonts/Roboto/Roboto-LICENSE.txt +2070 -0
  80. package/fonts/Roboto/Roboto-Regular.ttf +2070 -0
  81. package/fonts/WorkSans/WorkSans-LICENSE.txt +93 -0
  82. package/fonts/WorkSans/WorkSans-Regular.ttf +0 -0
  83. package/package.json +7 -3
package/README.md CHANGED
@@ -6,7 +6,9 @@
6
6
  [![npm version](https://badge.fury.io/js/appshot-cli.svg)](https://www.npmjs.com/package/appshot-cli)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
8
 
9
- 🆕 **Version 0.4.0** - Language-aware output directories, comprehensive font system, and system locale detection!
9
+ 🆕 **Version 0.7.0** - **Enhanced caption styling**, flexible positioning (above/below/overlay), customizable backgrounds and borders for professional App Store screenshots!
10
+
11
+ > ⚠️ **NEW in v0.7.0**: Complete caption styling system with backgrounds, borders, and flexible positioning. Create professional captions with customizable colors, opacity, padding, and rounded corners.
10
12
 
11
13
  > ⚠️ **BREAKING CHANGE in v0.4.0**: Output structure now always uses language subdirectories.
12
14
  > Single language builds now output to `final/device/lang/` instead of `final/device/`.
@@ -55,6 +57,7 @@ Appshot is the only **agent-first CLI tool** designed for automated App Store sc
55
57
  - 🖼️ **Smart Frames** - Automatically detects portrait/landscape and selects appropriate device frame
56
58
  - 🎨 **Gradient Presets** - 24+ beautiful gradients with visual preview and easy application
57
59
  - 🔤 **Font System** - 50+ font mappings, direct font setting, interactive selection, and system detection
60
+ - 📦 **Embedded Fonts** - 10 high-quality open source fonts bundled for consistent rendering everywhere
58
61
  - ✏️ **Dynamic Captions** - Smart text wrapping, auto-sizing, and multi-line support
59
62
  - 🌍 **AI Translation** - Real-time and batch translation using OpenAI's latest models
60
63
  - 📱 **Multi-Device** - iPhone, iPad, Mac, Apple TV, Vision Pro, and Apple Watch support
@@ -62,6 +65,8 @@ Appshot is the only **agent-first CLI tool** designed for automated App Store sc
62
65
  - 🔄 **Orientation Detection** - Intelligently handles both portrait and landscape
63
66
  - ⚡ **Parallel Processing** - Configurable concurrency for large batches
64
67
  - 🔍 **Caption Autocomplete** - Intelligent suggestions with fuzzy search and learning
68
+ - 🔬 **Dry-Run Mode** - Preview what would be built without generating images
69
+ - 🐛 **Verbose Debugging** - Detailed rendering metrics for troubleshooting
65
70
 
66
71
  ## 🚀 Quick Start
67
72
 
@@ -342,7 +347,7 @@ Intelligent caption rendering that adapts to content:
342
347
  ```json
343
348
  {
344
349
  "caption": {
345
- "position": "above", // above or overlay
350
+ "position": "above", // above, below, or overlay
346
351
  "box": {
347
352
  "autoSize": true, // Dynamic height
348
353
  "maxLines": 3, // Line limit
@@ -354,6 +359,97 @@ Intelligent caption rendering that adapts to content:
354
359
  }
355
360
  ```
356
361
 
362
+ #### Caption Positioning Options
363
+
364
+ - **`above`** (default): Caption appears above the device frame
365
+ - **`below`** (new in v0.7.0): Caption appears below the device frame
366
+ - **`overlay`**: Caption overlays on the gradient background
367
+
368
+ ```bash
369
+ # Configure caption below device
370
+ appshot style --device iphone
371
+ # → Select "Below device frame" option
372
+
373
+ # Or set directly in config
374
+ {
375
+ "devices": {
376
+ "iphone": {
377
+ "captionPosition": "below"
378
+ }
379
+ }
380
+ }
381
+ ```
382
+
383
+ #### Caption Styling (New in v0.7.0)
384
+
385
+ Create professional captions with customizable backgrounds and borders:
386
+
387
+ ```json
388
+ {
389
+ "caption": {
390
+ "color": "#FFFFFF", // Text color (hex)
391
+ "background": {
392
+ "color": "#000000", // Background color (hex)
393
+ "opacity": 0.8, // Transparency (0-1)
394
+ "padding": 20 // Padding around text
395
+ },
396
+ "border": {
397
+ "color": "#FFFFFF", // Border color (hex)
398
+ "width": 2, // Border thickness (1-10)
399
+ "radius": 12 // Rounded corners (0-30)
400
+ }
401
+ }
402
+ }
403
+ ```
404
+
405
+ **Key Features:**
406
+ - **Full-width styling** - Backgrounds and borders span device width for uniformity
407
+ - **Flexible positioning** - Works with above, below, and overlay positions
408
+ - **Device-specific overrides** - Customize styling per device type
409
+ - **Professional appearance** - Rounded corners and padding for polished look
410
+
411
+ **Interactive Configuration:**
412
+ ```bash
413
+ # Configure caption styling interactively
414
+ appshot style --device iphone
415
+ # → Choose caption position (above/below/overlay)
416
+ # → Configure background color and opacity
417
+ # → Set border color, width, and radius
418
+ ```
419
+
420
+ **Examples:**
421
+
422
+ ```json
423
+ // Dark background with white border
424
+ {
425
+ "caption": {
426
+ "color": "#FFFFFF",
427
+ "background": {
428
+ "color": "#000000",
429
+ "opacity": 0.9,
430
+ "padding": 30
431
+ },
432
+ "border": {
433
+ "color": "#FFFFFF",
434
+ "width": 3,
435
+ "radius": 16
436
+ }
437
+ }
438
+ }
439
+
440
+ // Subtle gradient-matched styling
441
+ {
442
+ "caption": {
443
+ "color": "#FFFFFF",
444
+ "background": {
445
+ "color": "#FF5733",
446
+ "opacity": 0.6,
447
+ "padding": 25
448
+ }
449
+ }
450
+ }
451
+ ```
452
+
357
453
  #### Caption Autocomplete
358
454
 
359
455
  The caption command includes intelligent autocomplete:
@@ -524,6 +620,8 @@ appshot build [options]
524
620
  - `--langs <list>` - Build for specific languages (if not specified, auto-detects)
525
621
  - `--preview` - Generate low-res previews
526
622
  - `--concurrency <n>` - Parallel processing limit (default: 5)
623
+ - `--dry-run` - Show what would be rendered without generating images
624
+ - `--verbose` - Show detailed rendering information
527
625
  - `--no-frame` - Skip device frames
528
626
  - `--no-gradient` - Skip gradient backgrounds
529
627
  - `--no-caption` - Skip captions
@@ -548,6 +646,12 @@ appshot build --preset iphone-6-9-portrait,ipad-13-landscape
548
646
 
549
647
  # Preview mode
550
648
  appshot build --preview --devices iphone
649
+
650
+ # Dry-run to see what would be built
651
+ appshot build --dry-run
652
+
653
+ # Verbose mode for debugging
654
+ appshot build --verbose --devices iphone
551
655
  ```
552
656
 
553
657
  **Exit Codes:**
@@ -685,7 +789,7 @@ Suggestions:
685
789
 
686
790
  ### `appshot fonts`
687
791
 
688
- Browse, validate, and set fonts for captions.
792
+ Browse, validate, and set fonts for captions. Includes 8 high-quality embedded fonts for consistent rendering across all platforms.
689
793
 
690
794
  ```bash
691
795
  appshot fonts [options]
@@ -693,29 +797,48 @@ appshot fonts [options]
693
797
 
694
798
  **Options:**
695
799
  - `--all` - List all system fonts
800
+ - `--embedded` - Show embedded fonts bundled with appshot
696
801
  - `--recommended` - Show recommended fonts only (default)
697
- - `--validate <name>` - Check if font is available
802
+ - `--validate <name>` - Check if font is available (embedded or system)
698
803
  - `--set <name>` - Set the caption font
699
804
  - `--select` - Interactive font selection
700
805
  - `--device <name>` - Target specific device (with --set or --select)
701
806
  - `--json` - Output as JSON
702
807
 
808
+ **Embedded Fonts (Always Available):**
809
+ - **Modern UI**: Inter, Poppins, Montserrat, DM Sans
810
+ - **Popular Web**: Roboto, Open Sans, Lato, Work Sans
811
+ - **Monospace**: JetBrains Mono, Fira Code
812
+ - **Variants**: Regular, Italic, Bold, and Bold Italic styles
813
+
814
+ All embedded fonts use open source licenses (OFL or Apache 2.0) and provide consistent rendering without requiring system installation. Font variants are automatically detected and properly rendered with correct styles.
815
+
703
816
  **Examples:**
704
817
  ```bash
705
818
  # Browse recommended fonts
706
819
  appshot fonts
707
820
 
708
- # Set global font directly
709
- appshot fonts --set "Montserrat"
821
+ # Show embedded fonts
822
+ appshot fonts --embedded
823
+
824
+ # Set global font directly (embedded font)
825
+ appshot fonts --set "Inter"
826
+
827
+ # Set font variant (italic style)
828
+ appshot fonts --set "Poppins Italic"
829
+
830
+ # Set bold variant
831
+ appshot fonts --set "Montserrat Bold"
710
832
 
711
833
  # Interactive font selection
712
834
  appshot fonts --select
713
835
 
714
- # Set device-specific font
715
- appshot fonts --set "SF Pro" --device iphone
836
+ # Set device-specific font variant
837
+ appshot fonts --set "Poppins Bold Italic" --device iphone
716
838
 
717
- # Validate before setting
718
- appshot fonts --validate "My Font" && appshot fonts --set "My Font"
839
+ # Validate font availability
840
+ appshot fonts --validate "Inter" # Shows: embedded
841
+ appshot fonts --validate "Arial" # Shows: system installed
719
842
 
720
843
  # List all system fonts
721
844
  appshot fonts --all
@@ -962,11 +1085,21 @@ appshot validate [options]
962
1085
  "caption": {
963
1086
  "font": "Font Name",
964
1087
  "fontsize": 64, // Pixels
965
- "color": "#FFFFFF",
1088
+ "color": "#FFFFFF", // Text color (hex)
966
1089
  "align": "center", // left, center, right
967
- "position": "above", // above, overlay
1090
+ "position": "above", // above, below, overlay
968
1091
  "paddingTop": 100,
969
1092
  "paddingBottom": 60,
1093
+ "background": { // Optional background styling
1094
+ "color": "#000000", // Background color (hex)
1095
+ "opacity": 0.8, // Transparency (0-1)
1096
+ "padding": 20 // Padding around text
1097
+ },
1098
+ "border": { // Optional border styling
1099
+ "color": "#FFFFFF", // Border color (hex)
1100
+ "width": 2, // Border thickness (1-10)
1101
+ "radius": 12 // Rounded corners (0-30)
1102
+ },
970
1103
  "box": {
971
1104
  "autoSize": true, // Dynamic height
972
1105
  "maxLines": 3,
@@ -1022,6 +1155,16 @@ Each device can override global settings:
1022
1155
  "captionFont": "SF Pro",
1023
1156
  "captionSize": 64,
1024
1157
  "captionPosition": "above",
1158
+ "captionBackground": { // Device-specific background
1159
+ "color": "#FF5733",
1160
+ "opacity": 0.7,
1161
+ "padding": 25
1162
+ },
1163
+ "captionBorder": { // Device-specific border
1164
+ "color": "#FFFFFF",
1165
+ "width": 3,
1166
+ "radius": 16
1167
+ },
1025
1168
  "captionBox": {
1026
1169
  "autoSize": false,
1027
1170
  "minHeight": 320,
@@ -1454,6 +1597,34 @@ appshot build --devices iphone
1454
1597
  appshot build --devices ipad
1455
1598
  ```
1456
1599
 
1600
+ ### Debugging with Verbose Mode
1601
+
1602
+ Use `--verbose` flag to diagnose rendering issues:
1603
+
1604
+ ```bash
1605
+ # See detailed caption metrics
1606
+ appshot build --verbose --devices iphone
1607
+
1608
+ # Output includes:
1609
+ # - Caption wrap width and line count
1610
+ # - Font stack with fallbacks
1611
+ # - Device frame scaling factors
1612
+ # - Position calculations
1613
+ ```
1614
+
1615
+ Use `--dry-run` to validate configuration without processing:
1616
+
1617
+ ```bash
1618
+ # Check what would be generated
1619
+ appshot build --dry-run
1620
+
1621
+ # Verify frame selection
1622
+ appshot build --dry-run --devices iphone
1623
+
1624
+ # Check multi-language output
1625
+ appshot build --dry-run --langs en,es,fr
1626
+ ```
1627
+
1457
1628
  ### Performance Tips
1458
1629
 
1459
1630
  1. **Use appropriate concurrency**
@@ -1590,7 +1761,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
1590
1761
 
1591
1762
  ### Completed ✅
1592
1763
  - [x] Official App Store specifications
1593
- - [x] Caption positioning (above/overlay)
1764
+ - [x] Caption positioning (above/below/overlay)
1594
1765
  - [x] Partial frame support
1595
1766
  - [x] Intelligent caption autocomplete
1596
1767
  - [x] Apple Watch optimizations
@@ -1632,7 +1803,7 @@ For security vulnerabilities, please see [SECURITY.md](SECURITY.md).
1632
1803
  ### NPM Package
1633
1804
 
1634
1805
  - 📦 [appshot-cli on NPM](https://www.npmjs.com/package/appshot-cli)
1635
- - 🔄 Latest version: 0.4.0
1806
+ - 🔄 Latest version: 0.6.0
1636
1807
  - ⬇️ Weekly downloads: ![npm](https://img.shields.io/npm/dw/appshot-cli)
1637
1808
 
1638
1809
  ---
package/dist/cli.js CHANGED
@@ -17,8 +17,40 @@ import { createCleanCommand } from './commands/clean.js';
17
17
  const program = new Command();
18
18
  program
19
19
  .name('appshot')
20
- .description('Generate App Store–ready screenshots with frames, gradients, and captions.')
21
- .version('0.5.0');
20
+ .description(`Generate App Store–ready screenshots with frames, gradients, and captions.
21
+
22
+ ${pc.bold('Features:')}
23
+ • Auto-detects portrait/landscape orientation
24
+ • 8 embedded font families with italic & bold variants
25
+ • 24+ gradient presets with visual preview
26
+ • AI-powered translation to 25+ languages
27
+ • Smart caption wrapping and positioning
28
+ • All official App Store resolutions
29
+ • Parallel processing for large batches
30
+
31
+ ${pc.bold('Quick Start:')}
32
+ $ appshot init # Initialize project
33
+ $ appshot caption --device iphone # Add captions
34
+ $ appshot build # Generate screenshots
35
+
36
+ ${pc.bold('Common Workflows:')}
37
+ $ appshot fonts --set "Poppins Italic" # Set italic font
38
+ $ appshot gradients select # Pick gradient
39
+ $ appshot build --preset iphone-6-9,ipad-13 # App Store presets
40
+ $ appshot localize --langs es,fr,de # Batch translate
41
+
42
+ ${pc.dim('Docs: https://github.com/chrisvanbuskirk/appshot')}`)
43
+ .version('0.7.0')
44
+ .addHelpText('after', `\n${pc.bold('Environment Variables:')}
45
+ OPENAI_API_KEY API key for translation features
46
+ APPSHOT_DISABLE_FONT_SCAN Skip system font detection (CI optimization)
47
+
48
+ ${pc.bold('Configuration Files:')}
49
+ .appshot/config.json Main configuration
50
+ .appshot/captions/*.json Per-device captions
51
+ .appshot/caption-history.json Autocomplete history
52
+
53
+ ${pc.dim('Run \'appshot <command> --help\' for command details.')}`);
22
54
  program.addCommand(initCmd());
23
55
  program.addCommand(captionCmd());
24
56
  program.addCommand(styleCmd());
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,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,QAAQ,MAAM,qBAAqB,CAAC;AAC3C,OAAO,UAAU,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,4EAA4E,CAAC;KACzF,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,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,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;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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,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,QAAQ,MAAM,qBAAqB,CAAC;AAC3C,OAAO,UAAU,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC;;EAEb,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC;;;;;;;;;EASpB,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC;;;;;EAKvB,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC;;;;;;EAM5B,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,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;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"}
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,MAAM,CAAC,OAAO,UAAU,QAAQ,YAuO/B"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,MAAM,CAAC,OAAO,UAAU,QAAQ,YA4T/B"}
@@ -9,7 +9,7 @@ import { composeAppStoreScreenshot } from '../core/compose.js';
9
9
  import { resolveLanguages, normalizeLanguageCode } from '../utils/language.js';
10
10
  export default function buildCmd() {
11
11
  const cmd = new Command('build')
12
- .description('Render screenshots using frames, gradients, and captions')
12
+ .description('Generate final screenshots with frames, gradients, and captions')
13
13
  .option('--devices <list>', 'comma-separated device list (e.g., iphone,ipad)', 'iphone,ipad,mac,watch')
14
14
  .option('--preset <ids>', 'use specific App Store presets (e.g., iphone-6-9,ipad-13)')
15
15
  .option('--config <file>', 'use specific config file', 'appshot.json')
@@ -19,11 +19,53 @@ export default function buildCmd() {
19
19
  .option('--no-frame', 'skip device frames')
20
20
  .option('--no-gradient', 'skip gradient backgrounds')
21
21
  .option('--no-caption', 'skip captions')
22
+ .option('--dry-run', 'show what would be rendered without generating images')
23
+ .option('--verbose', 'show detailed rendering information')
24
+ .addHelpText('after', `
25
+ ${pc.bold('Examples:')}
26
+ ${pc.dim('# Build all devices')}
27
+ $ appshot build
28
+
29
+ ${pc.dim('# Build specific devices')}
30
+ $ appshot build --devices iphone,ipad
31
+
32
+ ${pc.dim('# Build for multiple languages')}
33
+ $ appshot build --langs en,es,fr,de
34
+
35
+ ${pc.dim('# Use App Store presets')}
36
+ $ appshot build --preset iphone-6-9,ipad-13
37
+
38
+ ${pc.dim('# Fast preview mode')}
39
+ $ appshot build --preview --concurrency 8
40
+
41
+ ${pc.dim('# Frames only (no gradient/caption)')}
42
+ $ appshot build --no-gradient --no-caption
43
+
44
+ ${pc.dim('# Preview what would be built')}
45
+ $ appshot build --dry-run
46
+
47
+ ${pc.dim('# Show detailed rendering info')}
48
+ $ appshot build --verbose
49
+
50
+ ${pc.bold('Output:')}
51
+ Screenshots are saved to: ${pc.cyan('final/[device]/[language]/')}
52
+
53
+ ${pc.bold('Language Detection:')}
54
+ 1. --langs parameter (if provided)
55
+ 2. Languages in caption files
56
+ 3. defaultLanguage in config.json
57
+ 4. System locale
58
+ 5. Fallback to 'en'`)
22
59
  .action(async (opts) => {
23
60
  try {
24
- console.log(pc.bold('Building screenshots...'));
61
+ if (opts.dryRun) {
62
+ console.log(pc.bold('Dry run mode - no images will be generated\n'));
63
+ }
64
+ else {
65
+ console.log(pc.bold('Building screenshots...'));
66
+ }
25
67
  // Load configuration
26
- const config = await loadConfig();
68
+ const config = await loadConfig(opts.config);
27
69
  const devices = opts.devices.split(',').map((d) => d.trim());
28
70
  const concurrency = parseInt(opts.concurrency, 10);
29
71
  // Initialize frame registry from Frames.json if available
@@ -65,7 +107,7 @@ export default function buildCmd() {
65
107
  // Resolve languages for this device
66
108
  const cliLangs = opts.langs ? opts.langs.split(',').map((l) => normalizeLanguageCode(l.trim())) : undefined;
67
109
  const { languages, source } = resolveLanguages(cliLangs, captions, config);
68
- console.log(pc.cyan(`\n${device}:`), `Processing ${screenshots.length} screenshots`);
110
+ console.log(pc.cyan(`\n${device}:`), `${opts.dryRun ? 'Would process' : 'Processing'} ${screenshots.length} screenshots`);
69
111
  if (!cliLangs) {
70
112
  console.log(pc.dim(` Using language: ${languages.join(', ')} (from ${source})`));
71
113
  }
@@ -73,7 +115,9 @@ export default function buildCmd() {
73
115
  for (const lang of languages) {
74
116
  // Always use language subdirectory
75
117
  const langDir = path.join(outputDir, lang);
76
- await fs.mkdir(langDir, { recursive: true });
118
+ if (!opts.dryRun) {
119
+ await fs.mkdir(langDir, { recursive: true });
120
+ }
77
121
  // Process screenshots in batches
78
122
  for (let i = 0; i < screenshots.length; i += concurrency) {
79
123
  const batch = screenshots.slice(i, i + concurrency);
@@ -91,20 +135,22 @@ export default function buildCmd() {
91
135
  captionText = captionData[lang] || '';
92
136
  }
93
137
  // Get screenshot dimensions and orientation
94
- const { orientation } = await getImageDimensions(inputPath);
95
- // Load screenshot
138
+ const { width: srcWidth, height: srcHeight, orientation } = await getImageDimensions(inputPath);
139
+ // In dry-run mode, skip loading the screenshot buffer
96
140
  let screenshotBuffer;
97
- try {
98
- // First verify the file exists and is readable
99
- await fs.access(inputPath, fs.constants.R_OK);
100
- // Load the screenshot into a buffer
101
- screenshotBuffer = await sharp(inputPath)
102
- .png() // Ensure output is PNG
103
- .toBuffer();
104
- }
105
- catch (error) {
106
- console.error(pc.red(` ✗ ${path.basename(inputPath)}`), `Failed to load screenshot: ${error instanceof Error ? error.message : String(error)}`);
107
- return;
141
+ if (!opts.dryRun) {
142
+ try {
143
+ // First verify the file exists and is readable
144
+ await fs.access(inputPath, fs.constants.R_OK);
145
+ // Load the screenshot into a buffer
146
+ screenshotBuffer = await sharp(inputPath)
147
+ .png() // Ensure output is PNG
148
+ .toBuffer();
149
+ }
150
+ catch (error) {
151
+ console.error(pc.red(` ✗ ${path.basename(inputPath)}`), `Failed to load screenshot: ${error instanceof Error ? error.message : String(error)}`);
152
+ return;
153
+ }
108
154
  }
109
155
  // Parse resolution for output dimensions
110
156
  const [configWidth, configHeight] = deviceConfig.resolution.split('x').map(Number);
@@ -134,54 +180,85 @@ export default function buildCmd() {
134
180
  if (opts.frame !== false) {
135
181
  // If autoFrame is disabled but preferredFrame is set, use the preferred frame
136
182
  // Otherwise, auto-select a frame
137
- const result = await autoSelectFrame(inputPath, path.resolve(config.frames), device, deviceConfig.preferredFrame);
183
+ const result = await autoSelectFrame(inputPath, path.resolve(config.frames), device, deviceConfig.preferredFrame, opts.dryRun // Pass dry-run flag
184
+ );
138
185
  frame = result.frame;
139
186
  frameMetadata = result.metadata;
140
- if (frame && frameMetadata) {
187
+ if (frameMetadata) {
141
188
  frameUsed = true;
142
- console.log(pc.dim(` Using ${frameMetadata.displayName} ${orientation} frame`));
189
+ if (opts.verbose || opts.dryRun) {
190
+ console.log(pc.dim(` ${opts.dryRun ? 'Would use' : 'Using'} ${frameMetadata.displayName} ${orientation} frame`));
191
+ }
143
192
  }
144
- else if (frameMetadata && !frame) {
193
+ else if (!opts.dryRun && frameMetadata && !frame) {
145
194
  console.error(pc.red(' ERROR: Frame metadata found but image failed to load!'));
146
195
  }
147
196
  }
148
- // Use the new compose function
149
- let image;
150
- try {
151
- image = await composeAppStoreScreenshot({
152
- screenshot: screenshotBuffer,
153
- frame: frame,
154
- frameMetadata: frameMetadata ? {
155
- frameWidth: frameMetadata.frameWidth,
156
- frameHeight: frameMetadata.frameHeight,
157
- screenRect: frameMetadata.screenRect,
158
- maskPath: frameMetadata.maskPath,
159
- deviceType: frameMetadata.deviceType,
160
- displayName: frameMetadata.displayName,
161
- name: frameMetadata.name
162
- } : undefined,
163
- caption: opts.caption !== false ? captionText : undefined,
164
- captionConfig: config.caption,
165
- gradientConfig: config.gradient,
166
- deviceConfig: deviceConfig,
167
- outputWidth: outWidth,
168
- outputHeight: outHeight
169
- });
197
+ // Handle dry-run vs actual rendering
198
+ if (opts.dryRun) {
199
+ // Dry-run output
200
+ console.log(pc.cyan(` ${screenshot}`) + pc.dim(` → ${lang}`));
201
+ // Show source dimensions
202
+ console.log(pc.dim(` Source: ${srcWidth}x${srcHeight} (${orientation})`));
203
+ // Show frame info
204
+ if (frameMetadata) {
205
+ console.log(pc.dim(` Frame: ${frameMetadata.displayName} (${frameMetadata.frameWidth}x${frameMetadata.frameHeight})`));
206
+ }
207
+ else if (opts.frame !== false) {
208
+ console.log(pc.dim(' Frame: No matching frame found'));
209
+ }
210
+ // Show caption info
211
+ if (captionText && opts.caption !== false) {
212
+ const lines = captionText.split('\n').length;
213
+ console.log(pc.dim(` Caption: "${captionText.substring(0, 50)}${captionText.length > 50 ? '...' : ''}" (${captionText.length} chars, ${lines} line${lines > 1 ? 's' : ''})`));
214
+ // Show font info
215
+ const fontName = deviceConfig.captionFont || config.caption.font;
216
+ console.log(pc.dim(` Font: ${fontName}`));
217
+ }
218
+ // Show output info
219
+ console.log(pc.dim(` Output: ${outWidth}x${outHeight} → ${outputPath}`));
220
+ totalProcessed++;
170
221
  }
171
- catch (error) {
172
- console.error(pc.red(` ✗ ${path.basename(inputPath)}`), error instanceof Error ? error.message : String(error));
173
- return;
222
+ else {
223
+ // Actual rendering
224
+ let image;
225
+ try {
226
+ image = await composeAppStoreScreenshot({
227
+ screenshot: screenshotBuffer,
228
+ frame: frame,
229
+ frameMetadata: frameMetadata ? {
230
+ frameWidth: frameMetadata.frameWidth,
231
+ frameHeight: frameMetadata.frameHeight,
232
+ screenRect: frameMetadata.screenRect,
233
+ maskPath: frameMetadata.maskPath,
234
+ deviceType: frameMetadata.deviceType,
235
+ displayName: frameMetadata.displayName,
236
+ name: frameMetadata.name
237
+ } : undefined,
238
+ caption: opts.caption !== false ? captionText : undefined,
239
+ captionConfig: config.caption,
240
+ gradientConfig: config.gradient,
241
+ deviceConfig: deviceConfig,
242
+ outputWidth: outWidth,
243
+ outputHeight: outHeight,
244
+ verbose: opts.verbose
245
+ });
246
+ }
247
+ catch (error) {
248
+ console.error(pc.red(` ✗ ${path.basename(inputPath)}`), error instanceof Error ? error.message : String(error));
249
+ return;
250
+ }
251
+ // Save final image
252
+ await sharp(image)
253
+ .resize(opts.preview ? 800 : undefined, undefined, {
254
+ fit: 'inside',
255
+ withoutEnlargement: true
256
+ })
257
+ .png() // Ensure output is PNG
258
+ .toFile(outputPath);
259
+ console.log(pc.green(' ✓'), path.basename(outputPath), pc.dim(`[${orientation}${frameUsed ? ', framed' : ''}${captionText ? ', captioned' : ''}]`));
260
+ totalProcessed++;
174
261
  }
175
- // Save final image
176
- await sharp(image)
177
- .resize(opts.preview ? 800 : undefined, undefined, {
178
- fit: 'inside',
179
- withoutEnlargement: true
180
- })
181
- .png() // Ensure output is PNG
182
- .toFile(outputPath);
183
- console.log(pc.green(' ✓'), path.basename(outputPath), pc.dim(`[${orientation}${frameUsed ? ', framed' : ''}${captionText ? ', captioned' : ''}]`));
184
- totalProcessed++;
185
262
  }
186
263
  catch (error) {
187
264
  console.log(pc.red(' ✗'), screenshot, pc.dim(error instanceof Error ? error.message : String(error)));
@@ -193,12 +270,18 @@ export default function buildCmd() {
193
270
  }
194
271
  }
195
272
  // Summary
196
- console.log('\n' + pc.bold('Build complete!'));
197
- console.log(pc.green(`✓ ${totalProcessed} screenshots processed`));
198
- if (totalErrors > 0) {
199
- console.log(pc.red(`✗ ${totalErrors} errors`));
273
+ if (opts.dryRun) {
274
+ console.log('\n' + pc.bold('Dry run complete!'));
275
+ console.log(pc.cyan(`→ ${totalProcessed} screenshots would be generated`));
276
+ }
277
+ else {
278
+ console.log('\n' + pc.bold('Build complete!'));
279
+ console.log(pc.green(`✓ ${totalProcessed} screenshots processed`));
280
+ if (totalErrors > 0) {
281
+ console.log(pc.red(`✗ ${totalErrors} errors`));
282
+ }
283
+ console.log(pc.dim(`Output directory: ${config.output}`));
200
284
  }
201
- console.log(pc.dim(`Output directory: ${config.output}`));
202
285
  }
203
286
  catch (error) {
204
287
  console.error(pc.red('Error:'), error instanceof Error ? error.message : String(error));