appshot-cli 0.4.0 → 0.6.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 (87) hide show
  1. package/README.md +159 -17
  2. package/dist/cli.js +36 -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 +144 -61
  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/doctor.d.ts +3 -0
  11. package/dist/commands/doctor.d.ts.map +1 -0
  12. package/dist/commands/doctor.js +67 -0
  13. package/dist/commands/doctor.js.map +1 -0
  14. package/dist/commands/fonts.d.ts.map +1 -1
  15. package/dist/commands/fonts.js +89 -6
  16. package/dist/commands/fonts.js.map +1 -1
  17. package/dist/commands/gradients.d.ts.map +1 -1
  18. package/dist/commands/gradients.js +42 -2
  19. package/dist/commands/gradients.js.map +1 -1
  20. package/dist/commands/init.d.ts.map +1 -1
  21. package/dist/commands/init.js +20 -1
  22. package/dist/commands/init.js.map +1 -1
  23. package/dist/commands/localize.d.ts.map +1 -1
  24. package/dist/commands/localize.js +33 -1
  25. package/dist/commands/localize.js.map +1 -1
  26. package/dist/commands/specs.d.ts.map +1 -1
  27. package/dist/commands/specs.js +61 -46
  28. package/dist/commands/specs.js.map +1 -1
  29. package/dist/commands/style.d.ts.map +1 -1
  30. package/dist/commands/style.js +35 -2
  31. package/dist/commands/style.js.map +1 -1
  32. package/dist/core/app-store-specs.d.ts +2 -0
  33. package/dist/core/app-store-specs.d.ts.map +1 -1
  34. package/dist/core/app-store-specs.js +2 -0
  35. package/dist/core/app-store-specs.js.map +1 -1
  36. package/dist/core/compose.d.ts +6 -0
  37. package/dist/core/compose.d.ts.map +1 -1
  38. package/dist/core/compose.js +104 -4
  39. package/dist/core/compose.js.map +1 -1
  40. package/dist/core/devices.d.ts +1 -1
  41. package/dist/core/devices.d.ts.map +1 -1
  42. package/dist/core/devices.js +5 -1
  43. package/dist/core/devices.js.map +1 -1
  44. package/dist/services/doctor.d.ts +35 -0
  45. package/dist/services/doctor.d.ts.map +1 -0
  46. package/dist/services/doctor.js +439 -0
  47. package/dist/services/doctor.js.map +1 -0
  48. package/dist/services/fonts.d.ts +37 -2
  49. package/dist/services/fonts.d.ts.map +1 -1
  50. package/dist/services/fonts.js +223 -0
  51. package/dist/services/fonts.js.map +1 -1
  52. package/dist/types/exec.d.ts +5 -0
  53. package/dist/types/exec.d.ts.map +1 -0
  54. package/dist/types/exec.js +2 -0
  55. package/dist/types/exec.js.map +1 -0
  56. package/dist/types.d.ts +1 -0
  57. package/dist/types.d.ts.map +1 -1
  58. package/fonts/DMSans/DMSans-LICENSE.txt +93 -0
  59. package/fonts/DMSans/DMSans-Regular.ttf +0 -0
  60. package/fonts/Inter/InterVariable-Italic.ttf +0 -0
  61. package/fonts/Inter/InterVariable.ttf +0 -0
  62. package/fonts/Inter/LICENSE.txt +92 -0
  63. package/fonts/Lato/Lato-Bold.ttf +0 -0
  64. package/fonts/Lato/Lato-BoldItalic.ttf +0 -0
  65. package/fonts/Lato/Lato-Italic.ttf +0 -0
  66. package/fonts/Lato/Lato-LICENSE.txt +93 -0
  67. package/fonts/Lato/Lato-Regular.ttf +0 -0
  68. package/fonts/Montserrat/Montserrat-Bold.ttf +2070 -0
  69. package/fonts/Montserrat/Montserrat-BoldItalic.ttf +2070 -0
  70. package/fonts/Montserrat/Montserrat-Italic.ttf +2070 -0
  71. package/fonts/Montserrat/Montserrat-LICENSE.txt +93 -0
  72. package/fonts/Montserrat/Montserrat-Regular.ttf +2070 -0
  73. package/fonts/OpenSans/OpenSans-LICENSE.txt +92 -0
  74. package/fonts/OpenSans/OpenSans-Regular.ttf +0 -0
  75. package/fonts/Poppins/Poppins-Bold.ttf +0 -0
  76. package/fonts/Poppins/Poppins-BoldItalic.ttf +0 -0
  77. package/fonts/Poppins/Poppins-Italic.ttf +0 -0
  78. package/fonts/Poppins/Poppins-LICENSE.txt +93 -0
  79. package/fonts/Poppins/Poppins-Regular.ttf +0 -0
  80. package/fonts/Roboto/Roboto-Bold.ttf +2070 -0
  81. package/fonts/Roboto/Roboto-BoldItalic.ttf +2070 -0
  82. package/fonts/Roboto/Roboto-Italic.ttf +2070 -0
  83. package/fonts/Roboto/Roboto-LICENSE.txt +2070 -0
  84. package/fonts/Roboto/Roboto-Regular.ttf +2070 -0
  85. package/fonts/WorkSans/WorkSans-LICENSE.txt +93 -0
  86. package/fonts/WorkSans/WorkSans-Regular.ttf +0 -0
  87. package/package.json +7 -3
package/README.md CHANGED
@@ -6,7 +6,7 @@
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.6.0** - 8 embedded OSS fonts with variants, dry-run mode, verbose debugging, and enhanced CLI UX!
10
10
 
11
11
  > ⚠️ **BREAKING CHANGE in v0.4.0**: Output structure now always uses language subdirectories.
12
12
  > Single language builds now output to `final/device/lang/` instead of `final/device/`.
@@ -55,6 +55,7 @@ Appshot is the only **agent-first CLI tool** designed for automated App Store sc
55
55
  - 🖼️ **Smart Frames** - Automatically detects portrait/landscape and selects appropriate device frame
56
56
  - 🎨 **Gradient Presets** - 24+ beautiful gradients with visual preview and easy application
57
57
  - 🔤 **Font System** - 50+ font mappings, direct font setting, interactive selection, and system detection
58
+ - 📦 **Embedded Fonts** - 8 high-quality open source fonts bundled for consistent rendering everywhere
58
59
  - ✏️ **Dynamic Captions** - Smart text wrapping, auto-sizing, and multi-line support
59
60
  - 🌍 **AI Translation** - Real-time and batch translation using OpenAI's latest models
60
61
  - 📱 **Multi-Device** - iPhone, iPad, Mac, Apple TV, Vision Pro, and Apple Watch support
@@ -62,6 +63,8 @@ Appshot is the only **agent-first CLI tool** designed for automated App Store sc
62
63
  - 🔄 **Orientation Detection** - Intelligently handles both portrait and landscape
63
64
  - ⚡ **Parallel Processing** - Configurable concurrency for large batches
64
65
  - 🔍 **Caption Autocomplete** - Intelligent suggestions with fuzzy search and learning
66
+ - 🔬 **Dry-Run Mode** - Preview what would be built without generating images
67
+ - 🐛 **Verbose Debugging** - Detailed rendering metrics for troubleshooting
65
68
 
66
69
  ## 🚀 Quick Start
67
70
 
@@ -524,6 +527,8 @@ appshot build [options]
524
527
  - `--langs <list>` - Build for specific languages (if not specified, auto-detects)
525
528
  - `--preview` - Generate low-res previews
526
529
  - `--concurrency <n>` - Parallel processing limit (default: 5)
530
+ - `--dry-run` - Show what would be rendered without generating images
531
+ - `--verbose` - Show detailed rendering information
527
532
  - `--no-frame` - Skip device frames
528
533
  - `--no-gradient` - Skip gradient backgrounds
529
534
  - `--no-caption` - Skip captions
@@ -548,6 +553,12 @@ appshot build --preset iphone-6-9-portrait,ipad-13-landscape
548
553
 
549
554
  # Preview mode
550
555
  appshot build --preview --devices iphone
556
+
557
+ # Dry-run to see what would be built
558
+ appshot build --dry-run
559
+
560
+ # Verbose mode for debugging
561
+ appshot build --verbose --devices iphone
551
562
  ```
552
563
 
553
564
  **Exit Codes:**
@@ -627,9 +638,65 @@ appshot clean --all --yes
627
638
  appshot clean --history
628
639
  ```
629
640
 
641
+ ### `appshot doctor`
642
+
643
+ Run comprehensive system diagnostics to verify appshot installation and dependencies.
644
+
645
+ ```bash
646
+ appshot doctor [options]
647
+ ```
648
+
649
+ **Options:**
650
+ - `--json` - Output results as JSON for CI/automation
651
+ - `--verbose` - Show detailed diagnostic information
652
+ - `--category <categories>` - Run specific checks (comma-separated: system,dependencies,fonts,filesystem,frames)
653
+
654
+ **Checks:**
655
+ - **System Requirements** - Node.js version (≥18), npm availability, platform detection
656
+ - **Dependencies** - Sharp module installation, native bindings, image processing test, OpenAI API key
657
+ - **Font System** - Font detection commands, system font loading, common font availability
658
+ - **File System** - Write permissions (current/temp directories), .appshot directory, configuration validity
659
+ - **Frame Assets** - Frame directory presence, Frames.json validation, device frame counts, missing files
660
+
661
+ **Examples:**
662
+ ```bash
663
+ # Run all diagnostics
664
+ appshot doctor
665
+
666
+ # Check specific categories
667
+ appshot doctor --category system,dependencies
668
+
669
+ # JSON output for CI
670
+ appshot doctor --json
671
+
672
+ # Verbose mode for debugging
673
+ appshot doctor --verbose
674
+ ```
675
+
676
+ **Output Example:**
677
+ ```
678
+ 🏥 Appshot Doctor - System Diagnostics
679
+
680
+ System Requirements:
681
+ ✓ Node.js v20.5.0 (minimum: v18.0.0)
682
+ ✓ npm v9.8.0
683
+ ✓ darwin (macOS)
684
+
685
+ Dependencies:
686
+ ✓ Sharp v0.33.5 installed
687
+ ✓ libvips v8.15.3 loaded
688
+ ✓ Sharp image processing test passed
689
+ ⚠ OpenAI API key not found (translation features disabled)
690
+
691
+ Summary: 20 passed, 1 warning, 0 errors
692
+
693
+ Suggestions:
694
+ • Set OPENAI_API_KEY environment variable to enable translation features
695
+ ```
696
+
630
697
  ### `appshot fonts`
631
698
 
632
- Browse, validate, and set fonts for captions.
699
+ Browse, validate, and set fonts for captions. Includes 8 high-quality embedded fonts for consistent rendering across all platforms.
633
700
 
634
701
  ```bash
635
702
  appshot fonts [options]
@@ -637,29 +704,47 @@ appshot fonts [options]
637
704
 
638
705
  **Options:**
639
706
  - `--all` - List all system fonts
707
+ - `--embedded` - Show embedded fonts bundled with appshot
640
708
  - `--recommended` - Show recommended fonts only (default)
641
- - `--validate <name>` - Check if font is available
709
+ - `--validate <name>` - Check if font is available (embedded or system)
642
710
  - `--set <name>` - Set the caption font
643
711
  - `--select` - Interactive font selection
644
712
  - `--device <name>` - Target specific device (with --set or --select)
645
713
  - `--json` - Output as JSON
646
714
 
715
+ **Embedded Fonts (Always Available):**
716
+ - **Modern UI**: Inter, Poppins, Montserrat, DM Sans
717
+ - **Popular Web**: Roboto, Open Sans, Lato, Work Sans
718
+ - **Variants**: Regular, Italic, Bold, and Bold Italic styles
719
+
720
+ 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.
721
+
647
722
  **Examples:**
648
723
  ```bash
649
724
  # Browse recommended fonts
650
725
  appshot fonts
651
726
 
652
- # Set global font directly
653
- appshot fonts --set "Montserrat"
727
+ # Show embedded fonts
728
+ appshot fonts --embedded
729
+
730
+ # Set global font directly (embedded font)
731
+ appshot fonts --set "Inter"
732
+
733
+ # Set font variant (italic style)
734
+ appshot fonts --set "Poppins Italic"
735
+
736
+ # Set bold variant
737
+ appshot fonts --set "Montserrat Bold"
654
738
 
655
739
  # Interactive font selection
656
740
  appshot fonts --select
657
741
 
658
- # Set device-specific font
659
- appshot fonts --set "SF Pro" --device iphone
742
+ # Set device-specific font variant
743
+ appshot fonts --set "Poppins Bold Italic" --device iphone
660
744
 
661
- # Validate before setting
662
- appshot fonts --validate "My Font" && appshot fonts --set "My Font"
745
+ # Validate font availability
746
+ appshot fonts --validate "Inter" # Shows: embedded
747
+ appshot fonts --validate "Arial" # Shows: system installed
663
748
 
664
749
  # List all system fonts
665
750
  appshot fonts --all
@@ -798,21 +883,50 @@ appshot presets --generate iphone-6-9,ipad-13
798
883
 
799
884
  ### `appshot specs`
800
885
 
801
- Display device specifications.
886
+ Display complete Apple App Store screenshot specifications.
802
887
 
803
888
  ```bash
804
889
  appshot specs [options]
805
890
  ```
806
891
 
807
892
  **Options:**
808
- - `--device <name>` - Filter by device
809
- - `--json` - Output as JSON
893
+ - `--device <name>` - Filter by device type (iphone|ipad|mac|watch|appletv|visionpro)
894
+ - `--json` - Output as JSON (exact Apple specifications for diffing)
895
+ - `--required` - Show only required presets
810
896
 
811
897
  **Shows:**
812
- - Supported resolutions
813
- - Frame availability
814
- - Orientation support
815
- - App Store requirements
898
+ - Complete Apple specifications with exact resolutions
899
+ - Display sizes and device compatibility lists
900
+ - Required vs optional indicators
901
+ - Fallback notes and requirements
902
+
903
+ **JSON Output for Change Tracking:**
904
+ The `--json` flag outputs the complete Apple App Store specifications data, perfect for tracking changes over time:
905
+
906
+ ```bash
907
+ # Save current specifications
908
+ appshot specs --json > apple-specs-2024-08.json
909
+
910
+ # After Apple updates (typically September)
911
+ appshot specs --json > apple-specs-2024-09.json
912
+
913
+ # See what changed
914
+ diff apple-specs-2024-08.json apple-specs-2024-09.json
915
+ ```
916
+
917
+ **Data Source:**
918
+ The specifications mirror [Apple's official screenshot requirements](https://developer.apple.com/help/app-store-connect/reference/screenshot-specifications)
919
+ and are maintained in sync with Apple's updates. The JSON output preserves all metadata including:
920
+ - Exact resolutions (e.g., `1290x2796` for iPhone 6.9")
921
+ - Device groupings (which devices share requirements)
922
+ - Requirement status (mandatory vs optional)
923
+ - Fallback rules and special notes
924
+
925
+ This is particularly useful for:
926
+ - Tracking when Apple adds new device requirements
927
+ - Validating screenshot compliance before submission
928
+ - Automating screenshot generation pipelines
929
+ - Planning resource allocation for new devices
816
930
 
817
931
  ### `appshot style`
818
932
 
@@ -1369,6 +1483,34 @@ appshot build --devices iphone
1369
1483
  appshot build --devices ipad
1370
1484
  ```
1371
1485
 
1486
+ ### Debugging with Verbose Mode
1487
+
1488
+ Use `--verbose` flag to diagnose rendering issues:
1489
+
1490
+ ```bash
1491
+ # See detailed caption metrics
1492
+ appshot build --verbose --devices iphone
1493
+
1494
+ # Output includes:
1495
+ # - Caption wrap width and line count
1496
+ # - Font stack with fallbacks
1497
+ # - Device frame scaling factors
1498
+ # - Position calculations
1499
+ ```
1500
+
1501
+ Use `--dry-run` to validate configuration without processing:
1502
+
1503
+ ```bash
1504
+ # Check what would be generated
1505
+ appshot build --dry-run
1506
+
1507
+ # Verify frame selection
1508
+ appshot build --dry-run --devices iphone
1509
+
1510
+ # Check multi-language output
1511
+ appshot build --dry-run --langs en,es,fr
1512
+ ```
1513
+
1372
1514
  ### Performance Tips
1373
1515
 
1374
1516
  1. **Use appropriate concurrency**
@@ -1547,7 +1689,7 @@ For security vulnerabilities, please see [SECURITY.md](SECURITY.md).
1547
1689
  ### NPM Package
1548
1690
 
1549
1691
  - 📦 [appshot-cli on NPM](https://www.npmjs.com/package/appshot-cli)
1550
- - 🔄 Latest version: 0.4.0
1692
+ - 🔄 Latest version: 0.6.0
1551
1693
  - ⬇️ Weekly downloads: ![npm](https://img.shields.io/npm/dw/appshot-cli)
1552
1694
 
1553
1695
  ---
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ import localizeCmd from './commands/localize.js';
6
6
  import buildCmd from './commands/build.js';
7
7
  import specsCmd from './commands/specs.js';
8
8
  import checkCmd from './commands/check.js';
9
+ import doctorCmd from './commands/doctor.js';
9
10
  import presetsCmd from './commands/presets.js';
10
11
  import validateCmd from './commands/validate.js';
11
12
  import styleCmd from './commands/style.js';
@@ -16,8 +17,40 @@ import { createCleanCommand } from './commands/clean.js';
16
17
  const program = new Command();
17
18
  program
18
19
  .name('appshot')
19
- .description('Generate App Store–ready screenshots with frames, gradients, and captions.')
20
- .version('0.4.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.6.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.')}`);
21
54
  program.addCommand(initCmd());
22
55
  program.addCommand(captionCmd());
23
56
  program.addCommand(styleCmd());
@@ -27,6 +60,7 @@ program.addCommand(localizeCmd());
27
60
  program.addCommand(buildCmd());
28
61
  program.addCommand(specsCmd());
29
62
  program.addCommand(checkCmd());
63
+ program.addCommand(doctorCmd());
30
64
  program.addCommand(presetsCmd());
31
65
  program.addCommand(validateCmd());
32
66
  program.addCommand(migrateCmd());
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,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,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,9 +19,51 @@ 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
68
  const config = await loadConfig();
27
69
  const devices = opts.devices.split(',').map((d) => d.trim());
@@ -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));