mobile-snap 1.0.3 → 1.0.5

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 (4) hide show
  1. package/README.html +62 -62
  2. package/README.md +83 -78
  3. package/bin/cli.js +48 -48
  4. package/package.json +1 -1
package/README.html CHANGED
@@ -1,9 +1,9 @@
1
1
  <!DOCTYPE html>
2
- <html lang="id">
2
+ <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>MobileSnap - Dokumentasi & Arsitektur</title>
6
+ <title>MobileSnap - Documentation & Architecture</title>
7
7
  <!-- Google Fonts -->
8
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -586,7 +586,7 @@
586
586
  <div class="header-glow"></div>
587
587
  <div class="container">
588
588
  <h1>MobileSnap šŸ“ø</h1>
589
- <p class="tagline">Otomasi Pixel-Precise App Store & Play Store Screenshots dari Server Lokal secara Dual-Vendor menggunakan NPX.</p>
589
+ <p class="tagline">Automate pixel-precise App Store & Google Play screenshots directly from your local development server using NPX.</p>
590
590
 
591
591
  <div class="badge-container">
592
592
  <div class="badge">
@@ -603,10 +603,10 @@
603
603
  </header>
604
604
 
605
605
  <div class="container">
606
- <!-- Contoh Output (Mockup Premium) -->
606
+ <!-- Sample Output Mockups -->
607
607
  <section class="glass-card">
608
- <h2>šŸ“± Contoh Output (Mockup Premium)</h2>
609
- <p style="color: var(--text-muted); margin-bottom: 1.5rem;">Visualisasi nyata dari tangkapan layar yang dibungkus otomatis ke dalam bingkai mockup perangkat premium menggunakan opsi <code>-m</code> atau <code>--mockup</code>:</p>
608
+ <h2>šŸ“± Sample Output (Premium Mockups)</h2>
609
+ <p style="color: var(--text-muted); margin-bottom: 1.5rem;">Real-world visualization of screenshots automatically wrapped in premium device mockup frames using the <code>-m</code> or <code>--mockup</code> option:</p>
610
610
  <div style="display: flex; justify-content: center; gap: 2rem; flex-wrap: wrap; margin-top: 1.5rem;">
611
611
  <div style="text-align: center; max-width: 45%; min-width: 280px;">
612
612
  <h4 style="margin-bottom: 0.5rem; color: #fff;">iOS (iPhone 6.7" Pro Max)</h4>
@@ -619,38 +619,38 @@
619
619
  </div>
620
620
  </section>
621
621
 
622
- <!-- Arsitektur Utama -->
622
+ <!-- Workflow Architecture -->
623
623
  <section class="glass-card">
624
- <h2>šŸ—ļø Arsitektur Aliran Kerja</h2>
625
- <p style="color: var(--text-muted); margin-bottom: 1.5rem;">MobileSnap dibangun menggunakan <strong>Commander</strong> untuk antarmuka CLI yang kaya, dan <strong>Playwright Node API</strong> untuk mengontrol Chromium secara berurutan pada berbagai dimensi.</p>
624
+ <h2>šŸ—ļø Workflow Architecture</h2>
625
+ <p style="color: var(--text-muted); margin-bottom: 1.5rem;">MobileSnap is built using <strong>Commander</strong> for a rich CLI interface, and the <strong>Playwright Node API</strong> to control Chromium sequentially across various dimensions.</p>
626
626
 
627
627
  <div class="arch-flow">
628
628
  <div class="arch-node">
629
629
  <div class="node-num">1</div>
630
630
  <h4>CLI Parser</h4>
631
- <p>Membaca parameter URL, rute (paths), platform target, dan folder keluaran.</p>
631
+ <p>Parses URL parameters, routes (paths), target platforms, and the output directory.</p>
632
632
  </div>
633
633
  <div class="arch-node">
634
634
  <div class="node-num">2</div>
635
635
  <h4>Browser Engine</h4>
636
- <p>Inisiasi Chromium secara terisolasi dan asinkronus.</p>
636
+ <p>Launches Chromium in an isolated and asynchronous instance.</p>
637
637
  </div>
638
638
  <div class="arch-node">
639
639
  <div class="node-num">3</div>
640
640
  <h4>Viewport Emulation</h4>
641
- <p>Meniru dimensi layar Retina (iOS) & Android secara dinamis.</p>
641
+ <p>Dynamically emulates Retina (iOS) & Android display dimensions.</p>
642
642
  </div>
643
643
  <div class="arch-node">
644
644
  <div class="node-num">4</div>
645
645
  <h4>Hydration Check</h4>
646
- <p>Menunggu aktivitas jaringan idle agar rendering JavaScript selesai.</p>
646
+ <p>Waits for network idle state to ensure JavaScript rendering is fully complete.</p>
647
647
  </div>
648
648
  </div>
649
649
  </section>
650
650
 
651
- <!-- Target Dimensi -->
651
+ <!-- Target Dimensions Requirement -->
652
652
  <section class="glass-card">
653
- <h2>šŸ“± Dimensi Layar Target (Wajib Store)</h2>
653
+ <h2>šŸ“± Target Screen Dimensions (Store Requirements)</h2>
654
654
 
655
655
  <h3>šŸŽ iOS (Apple App Store)</h3>
656
656
  <div class="device-showcase">
@@ -662,8 +662,8 @@
662
662
  <div class="device-info">
663
663
  <h4>iPhone 6.7" Display</h4>
664
664
  <div class="resolution">1290 x 2796 pixels</div>
665
- <p>Digunakan untuk jajaran iPhone Max/Pro Max terbaru.</p>
666
- <p style="margin-top: 0.5rem; font-family: 'JetBrains Mono', monospace; font-size: 0.75rem;">Berkas: 6.7_inch_[path].png</p>
665
+ <p>Used for the latest iPhone Max/Pro Max models.</p>
666
+ <p style="margin-top: 0.5rem; font-family: 'JetBrains Mono', monospace; font-size: 0.75rem;">File: 6.7_inch_[path].png</p>
667
667
  </div>
668
668
  </div>
669
669
 
@@ -675,8 +675,8 @@
675
675
  <div class="device-info">
676
676
  <h4>iPhone 6.5" Display</h4>
677
677
  <div class="resolution">1242 x 2688 pixels</div>
678
- <p>Digunakan untuk seri iPhone Xs Max / 11 Pro Max.</p>
679
- <p style="margin-top: 0.5rem; font-family: 'JetBrains Mono', monospace; font-size: 0.75rem;">Berkas: 6.5_inch_[path].png</p>
678
+ <p>Used for the iPhone Xs Max / 11 Pro Max series.</p>
679
+ <p style="margin-top: 0.5rem; font-family: 'JetBrains Mono', monospace; font-size: 0.75rem;">File: 6.5_inch_[path].png</p>
680
680
  </div>
681
681
  </div>
682
682
  </div>
@@ -691,8 +691,8 @@
691
691
  <div class="device-info">
692
692
  <h4>Android Phone</h4>
693
693
  <div class="resolution">1080 x 2400 pixels</div>
694
- <p>Rasio aspek modern 20:9 untuk Google Play Phone screenshots.</p>
695
- <p style="margin-top: 0.5rem; font-family: 'JetBrains Mono', monospace; font-size: 0.75rem;">Berkas: android_phone_[path].png</p>
694
+ <p>Modern 20:9 aspect ratio for Google Play Phone screenshots.</p>
695
+ <p style="margin-top: 0.5rem; font-family: 'JetBrains Mono', monospace; font-size: 0.75rem;">File: android_phone_[path].png</p>
696
696
  </div>
697
697
  </div>
698
698
 
@@ -704,17 +704,17 @@
704
704
  <div class="device-info">
705
705
  <h4>Android Tablet (10")</h4>
706
706
  <div class="resolution">1600 x 2560 pixels</div>
707
- <p>Dimensi tablet standar untuk kebutuhan rilis Google Play Tablet.</p>
708
- <p style="margin-top: 0.5rem; font-family: 'JetBrains Mono', monospace; font-size: 0.75rem;">Berkas: android_tablet_[path].png</p>
707
+ <p>Standard tablet dimensions for Google Play Tablet release requirements.</p>
708
+ <p style="margin-top: 0.5rem; font-family: 'JetBrains Mono', monospace; font-size: 0.75rem;">File: android_tablet_[path].png</p>
709
709
  </div>
710
710
  </div>
711
711
  </div>
712
712
  </section>
713
713
 
714
- <!-- Cara Instalasi -->
714
+ <!-- NPX Usage -->
715
715
  <section class="glass-card">
716
- <h2>šŸš€ Cara Penggunaan Instan (NPX)</h2>
717
- <p style="color: var(--text-muted); margin-bottom: 1rem;">Anda tidak perlu melakukan penyiapan manual. Cukup gunakan <code>npx</code> untuk menjalankan MobileSnap.</p>
716
+ <h2>šŸš€ Instant Usage (NPX)</h2>
717
+ <p style="color: var(--text-muted); margin-bottom: 1rem;">You do not need any manual setup. Simply use <code>npx</code> to run MobileSnap instantly.</p>
718
718
 
719
719
  <div class="terminal">
720
720
  <div class="terminal-header">
@@ -728,38 +728,38 @@
728
728
  <div class="terminal-body">
729
729
  <button class="btn-copy" onclick="copyText('setup-cmds')">Copy</button>
730
730
  <pre id="setup-cmds" style="font-family: inherit;">
731
- <span class="terminal-comment"># 1. Jalankan instan menggunakan npx (ganti port sesuai server lokal Anda)</span>
731
+ <span class="terminal-comment"># 1. Run instantly using npx (change port according to your local server)</span>
732
732
  <span class="terminal-prompt">PS></span>npx mobile-snap --url http://localhost:4321
733
733
 
734
- <span class="terminal-comment"># 2. Opsional: Unduh browser binaries jika Playwright belum dikonfigurasi di sistem</span>
734
+ <span class="terminal-comment"># 2. Optional: Download browser binaries if Playwright is not yet configured in the system</span>
735
735
  <span class="terminal-prompt">PS></span>npx playwright install chromium</pre>
736
736
  </div>
737
737
  </div>
738
738
  </section>
739
739
 
740
- <!-- Interaktif Command Builder -->
740
+ <!-- Command Builder -->
741
741
  <section class="glass-card">
742
742
  <h2>⚔ CLI Command Builder (NPM / NPX)</h2>
743
- <p style="color: var(--text-muted);">Gunakan builder interaktif ini untuk membuat dan menyalin perintah sesuai kebutuhan pengembangan Anda:</p>
743
+ <p style="color: var(--text-muted);">Use this interactive builder to generate and copy the command that fits your development needs:</p>
744
744
 
745
745
  <div class="builder-container">
746
746
  <div class="grid-2">
747
747
  <div class="form-group">
748
- <label for="input-url">URL Dev Server Lokal</label>
748
+ <label for="input-url">Local Dev Server URL</label>
749
749
  <input type="text" id="input-url" class="form-control" value="http://localhost:4321" oninput="updateCommand()">
750
750
  </div>
751
751
  <div class="form-group">
752
- <label for="input-paths">Rute / Paths (pisahkan koma)</label>
752
+ <label for="input-paths">Routes / Paths (comma-separated)</label>
753
753
  <input type="text" id="input-paths" class="form-control" value="/" oninput="updateCommand()">
754
754
  </div>
755
755
  </div>
756
756
  <div class="grid-4">
757
757
  <div class="form-group">
758
- <label for="input-platform">Platform Target</label>
758
+ <label for="input-platform">Target Platform</label>
759
759
  <select id="input-platform" class="form-control" onchange="updateCommand()">
760
760
  <option value="ios">iOS (App Store)</option>
761
761
  <option value="android">Android (Play Store)</option>
762
- <option value="both">Both (iOS & Android Sekaligus)</option>
762
+ <option value="both">Both (iOS & Android Simultaneously)</option>
763
763
  </select>
764
764
  </div>
765
765
  <div class="form-group" style="display: flex; align-items: center; gap: 0.5rem; margin-top: 1.5rem;">
@@ -776,7 +776,7 @@
776
776
  </div>
777
777
  </div>
778
778
  <div class="form-group" style="margin-top: 1rem;">
779
- <label for="input-output">Folder Output</label>
779
+ <label for="input-output">Output Folder</label>
780
780
  <input type="text" id="input-output" class="form-control" value="mobilesnap_output" oninput="updateCommand()">
781
781
  </div>
782
782
 
@@ -787,121 +787,121 @@
787
787
  </div>
788
788
  </section>
789
789
 
790
- <!-- Detail Opsi Parameter -->
790
+ <!-- Parameters Table -->
791
791
  <section class="glass-card">
792
- <h2>āš™ļø Parameter & Opsi CLI</h2>
792
+ <h2>āš™ļø CLI Parameters & Options</h2>
793
793
  <table class="option-table">
794
794
  <thead>
795
795
  <tr>
796
- <th>Parameter</th>
796
+ <th>Option</th>
797
797
  <th>Alias</th>
798
- <th>Deskripsi</th>
799
- <th>Bawaan (Default)</th>
798
+ <th>Description</th>
799
+ <th>Default</th>
800
800
  </tr>
801
801
  </thead>
802
802
  <tbody>
803
803
  <tr>
804
804
  <td><span class="param-tag">--url</span></td>
805
805
  <td><span class="param-tag">-u</span></td>
806
- <td><strong>(Wajib)</strong> URL server lokal yang sedang berjalan (contoh: Astro).</td>
806
+ <td><strong>(Required)</strong> URL of the local development server (e.g., Astro).</td>
807
807
  <td>-</td>
808
808
  </tr>
809
809
  <tr>
810
810
  <td><span class="param-tag">--paths</span></td>
811
811
  <td><span class="param-tag">-p</span></td>
812
- <td>Daftar rute halaman yang dipisahkan dengan tanda koma.</td>
812
+ <td>Comma-separated list of routes to capture.</td>
813
813
  <td><code style="color: var(--secondary);">"/"</code></td>
814
814
  </tr>
815
815
  <tr>
816
816
  <td><span class="param-tag">--platform</span></td>
817
817
  <td><span class="param-tag">-l</span></td>
818
- <td>Platform yang ingin ditangkap: <code style="color: var(--secondary);">"ios"</code>, <code style="color: var(--secondary);">"android"</code>, atau <code style="color: var(--secondary);">"both"</code>.</td>
818
+ <td>Target platform to capture: <code style="color: var(--secondary);">"ios"</code>, <code style="color: var(--secondary);">"android"</code>, or <code style="color: var(--secondary);">"both"</code>.</td>
819
819
  <td><code style="color: var(--secondary);">"ios"</code></td>
820
820
  </tr>
821
821
  <tr>
822
822
  <td><span class="param-tag">--output</span></td>
823
823
  <td><span class="param-tag">-o</span></td>
824
- <td>Folder tujuan untuk menaruh berkas gambar hasil tangkapan.</td>
824
+ <td>Destination directory to save the screenshot files.</td>
825
825
  <td><code style="color: var(--secondary);">"mobilesnap_output"</code></td>
826
826
  </tr>
827
827
  <tr>
828
828
  <td><span class="param-tag">--crawl</span></td>
829
829
  <td><span class="param-tag">-c</span></td>
830
- <td>Menelusuri secara otomatis semua link internal dari halaman beranda.</td>
830
+ <td>Automatically discover and screenshot all internal links starting from the home page.</td>
831
831
  <td><code style="color: var(--secondary);">false</code></td>
832
832
  </tr>
833
833
  <tr>
834
834
  <td><span class="param-tag">--detect-pages</span></td>
835
835
  <td><span class="param-tag">-d</span></td>
836
- <td>Memindai direktori folder proyek lokal (Astro/Next.js) untuk rute statis.</td>
836
+ <td>Scan the local project directories (Astro/Next.js) for static routes.</td>
837
837
  <td><code style="color: var(--secondary);">false</code></td>
838
838
  </tr>
839
839
  <tr>
840
840
  <td><span class="param-tag">--email</span></td>
841
841
  <td>-</td>
842
- <td>Email / Username untuk autentikasi otomatis jika halaman memerlukan login.</td>
842
+ <td>Email / Username for automatic authentication if the page requires login.</td>
843
843
  <td>-</td>
844
844
  </tr>
845
845
  <tr>
846
846
  <td><span class="param-tag">--password</span></td>
847
847
  <td>-</td>
848
- <td>Password untuk autentikasi otomatis jika halaman memerlukan login (input disensor).</td>
848
+ <td>Password for automatic authentication if the page requires login (masked input).</td>
849
849
  <td>-</td>
850
850
  </tr>
851
851
  <tr>
852
852
  <td><span class="param-tag">--login-path</span></td>
853
853
  <td>-</td>
854
- <td>Jalur rute URL ke halaman login.</td>
854
+ <td>URL route path to the login page.</td>
855
855
  <td><code style="color: var(--secondary);">"/login.html"</code></td>
856
856
  </tr>
857
857
  <tr>
858
858
  <td><span class="param-tag">--html</span></td>
859
859
  <td>-</td>
860
- <td>Menambahkan ekstensi .html otomatis ke rute statis yang terdeteksi.</td>
860
+ <td>Automatically append .html extension to detected static routes.</td>
861
861
  <td><code style="color: var(--secondary);">false</code></td>
862
862
  </tr>
863
863
  <tr>
864
864
  <td><span class="param-tag">--mockup</span></td>
865
865
  <td><span class="param-tag">-m</span></td>
866
- <td>Membungkus tangkapan layar dalam bingkai mockup perangkat (iPhone/Android) dengan status bar dan bayangan transparan.</td>
866
+ <td>Wrap screenshots in a beautiful device mockup frame (iPhone/Android) with status bars and transparent drop shadows.</td>
867
867
  <td><code style="color: var(--secondary);">false</code></td>
868
868
  </tr>
869
869
  </tbody>
870
870
  </table>
871
871
  </section>
872
872
 
873
- <!-- Penanganan Masalah & Ketahanan -->
873
+ <!-- Resilience and Special Features -->
874
874
  <section class="glass-card">
875
- <h2>šŸ›”ļø Ketahanan Sistem & Fitur Khusus</h2>
875
+ <h2>šŸ›”ļø System Resilience & Special Features</h2>
876
876
 
877
877
  <div class="alert-box alert-warning">
878
878
  <div class="alert-icon">šŸ’”</div>
879
879
  <div class="alert-text">
880
- <h5>Skema Protokol URL & Auto-Normalisasi</h5>
881
- <p>MobileSnap secara otomatis mendeteksi jika URL tidak memiliki protokol (seperti <code>localhost:3000</code>) dan menambahkan <code>http://</code> secara dinamis.</p>
880
+ <h5>URL Protocol & Auto-Normalization</h5>
881
+ <p>MobileSnap automatically detects if the URL lacks a protocol prefix (e.g., <code>localhost:3000</code>) and prepends <code>http://</code> dynamically.</p>
882
882
  </div>
883
883
  </div>
884
884
 
885
885
  <div class="alert-box alert-warning">
886
886
  <div class="alert-icon">🌐</div>
887
887
  <div class="alert-text">
888
- <h5>User-Agent Dinamis per Platform</h5>
889
- <p>Ketika platform Android berjalan, mesin otomatisasi mengemulasi Agen Pengguna Android 13 (Pixel 7). Sedangkan untuk platform iOS, digunakan Agen Pengguna iPhone. Ini menjamin server memberikan respon tampilan yang tepat sesuai target vendor.</p>
888
+ <h5>Dynamic User-Agents per Platform</h5>
889
+ <p>When capturing for Android, the system emulates Android 13 (Pixel 7) user-agent. For iOS, it emulates iPhone. This guarantees that your server serves the appropriate layout for each specific vendor.</p>
890
890
  </div>
891
891
  </div>
892
892
 
893
893
  <div class="alert-box alert-warning">
894
894
  <div class="alert-icon">šŸ“</div>
895
895
  <div class="alert-text">
896
- <h5>Skala Resolusi Retina (DPI 3x)</h5>
897
- <p>Semua screenshot ditangkap dengan pengaturan <code>deviceScaleFactor: 3</code> untuk menjaga kualitas output tetap tajam untuk standar unggahan store resmi.</p>
896
+ <h5>Retina Resolution Scale (DPI 3x)</h5>
897
+ <p>All screenshots are captured with <code>deviceScaleFactor: 3</code> to ensure the output remains ultra-sharp, complying with official store upload standards.</p>
898
898
  </div>
899
899
  </div>
900
900
  </section>
901
901
  </div>
902
902
 
903
903
  <footer>
904
- <p>&copy; 2026 MobileSnap CLI Tool. Dibuat dengan presisi visual untuk kebutuhan App Store & Play Store Anda.</p>
904
+ <p>&copy; 2026 MobileSnap CLI Tool. Crafted with visual precision for all your App Store & Play Store requirements.</p>
905
905
  </footer>
906
906
 
907
907
  <script>
@@ -942,9 +942,9 @@
942
942
  const textToCopy = isInner ? element.innerText : element.textContent;
943
943
 
944
944
  navigator.clipboard.writeText(textToCopy).then(() => {
945
- alert('Teks berhasil disalin ke clipboard!');
945
+ alert('Command copied to clipboard successfully!');
946
946
  }).catch(err => {
947
- console.error('Gagal menyalin teks: ', err);
947
+ console.error('Failed to copy text: ', err);
948
948
  });
949
949
  }
950
950
  </script>
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # MobileSnap šŸ“ø
2
2
 
3
- MobileSnap adalah alat CLI (Command Line Interface) berbasis Node.js yang dirancang untuk mengotomatisasi pengambilan tangkapan layar (screenshot) App Store & Google Play Store dengan presisi piksel tinggi langsung dari server pengembangan lokal (seperti Astro, Next.js, React, atau Vue).
3
+ MobileSnap is a Node.js-based CLI (Command Line Interface) tool designed to automate the capture of pixel-precise App Store and Google Play Store screenshots directly from local development servers (such as Astro, Next.js, React, or Vue).
4
4
 
5
- ### šŸ“± Contoh Output (Mockup Premium)
5
+ ### šŸ“± Sample Output (Premium Mockups)
6
6
 
7
- Berikut adalah visualisasi nyata dari tangkapan layar yang dibungkus otomatis ke dalam bingkai mockup perangkat premium (menggunakan opsi `-m` atau `--mockup`):
7
+ Below is a real-world visualization of screenshots automatically wrapped in premium device mockup frames (using the `-m` or `--mockup` option):
8
8
 
9
9
  | iOS (iPhone 6.7" Pro Max) | Android Phone (Google Pixel 7) |
10
10
  | :---: | :---: |
@@ -12,148 +12,147 @@ Berikut adalah visualisasi nyata dari tangkapan layar yang dibungkus otomatis ke
12
12
 
13
13
  ---
14
14
 
15
- ## šŸ—ļø Arsitektur Sistem
15
+ ## šŸ—ļø System Architecture
16
16
 
17
- MobileSnap dirancang dengan fokus pada efisiensi, keandalan, dan kemudahan penggunaan. Berikut adalah diagram alur kerja utama aplikasi:
17
+ MobileSnap is designed with a focus on efficiency, reliability, and ease of use. Below is the main workflow diagram of the application:
18
18
 
19
19
  ```mermaid
20
20
  graph TD
21
- A[Pengguna menjalankan perintah CLI npx] --> B[Commander CLI Parser]
22
- B --> C{Validasi Argumen & URL}
23
- C -->|URL Valid| D[Inisialisasi Playwright Async]
24
- C -->|URL Tidak Valid| E[Console Error & Exit]
25
- D --> F[Buka Headless Chromium Browser]
26
- F --> G[Looping berdasarkan Pilihan Platform: iOS / Android / Both]
27
- G --> H[Konfigurasi Context & Viewport Emulator Perangkat]
28
- H --> I[Looping berdasarkan Jalur/Rute URL]
29
- I --> J[Navigasi Halaman & Tunggu State Network Idle]
30
- J --> K[Ambil Tangkapan Layar & Simpan File]
31
- K --> L[Tutup Context Browser]
32
- L --> M[Selesai & Tampilkan Ringkasan]
21
+ A[User runs npx CLI command] --> B[Commander CLI Parser]
22
+ B --> C{Validate Arguments & URL}
23
+ C -->|Valid URL| D[Initialize Playwright Async]
24
+ C -->|Invalid URL| E[Console Error & Exit]
25
+ D --> F[Launch Headless Chromium Browser]
26
+ F --> G[Loop through Platform Choices: iOS / Android / Both]
27
+ G --> H[Configure Device Emulator Context & Viewport]
28
+ H --> I[Loop through URL Paths/Routes]
29
+ I --> J[Navigate to Page & Wait for Network Idle]
30
+ J --> K[Capture Screenshot & Save File]
31
+ K --> L[Close Browser Context]
32
+ L --> M[Done & Display Summary]
33
33
  ```
34
34
 
35
- ### Komponen Utama
35
+ ### Main Components
36
36
 
37
- 1. **Parser CLI ([bin/cli.js](file:///d:/Deweb/MobileSnap/bin/cli.js))**: Menggunakan library `Commander` untuk memproses input parameter dari pengguna secara intuitif.
38
- 2. **Mesin Otomatisasi Browser**: Berbasis `Playwright` untuk menjalankan proses Chromium tanpa kepala (*headless*).
39
- 3. **Pengaturan Emulator Presisi**:
40
- - **Skala Perangkat (DPI)**: Ditetapkan ke `deviceScaleFactor: 3` untuk menghasilkan kualitas tangkapan layar yang sangat tajam (Retina/High DPI) sesuai standar rilis.
41
- - **Agen Pengguna (User Agent)**: Dikonfigurasi dinamis sesuai platform target (iOS menggunakan user agent iPhone, Android menggunakan user agent Google Pixel 7).
42
- 4. **Sinkronisasi Hidrasi Web**: Menggunakan `page.waitForLoadState("networkidle")` untuk mendeteksi ketika semua aset selesai dimuat sebelum tangkapan layar diambil. Ini sangat penting untuk framework modern seperti Astro.
37
+ 1. **CLI Parser ([bin/cli.js](file:///d:/Deweb/MobileSnap/bin/cli.js))**: Uses the `Commander` library to process user input parameters intuitively.
38
+ 2. **Browser Automation Engine**: Powered by `Playwright` to run headless Chromium instances.
39
+ 3. **Precision Emulator Configuration**:
40
+ - **Device Scale (DPI)**: Set to `deviceScaleFactor: 3` to produce ultra-sharp screenshots (Retina/High DPI) meeting official store release guidelines.
41
+ - **User Agent**: Configured dynamically based on the target platform (iOS uses an iPhone user agent; Android uses a Google Pixel 7 user agent).
42
+ 4. **Web Hydration Synchronization**: Employs `page.waitForLoadState("networkidle")` to detect when all page assets are fully loaded before capturing the screenshot. This is crucial for modern frameworks like Astro.
43
43
 
44
44
  ---
45
45
 
46
- ## šŸ“± Spesifikasi Dimensi Target
46
+ ## šŸ“± Target Dimension Specifications
47
47
 
48
- MobileSnap secara otomatis mengambil gambar untuk perangkat berikut berdasarkan platform yang dipilih:
48
+ MobileSnap automatically captures screenshots for the following devices based on the selected platform:
49
49
 
50
50
  ### iOS (Apple App Store)
51
- | Nama Layar | Resolusi (Piksel) | Rasio Aspek | Output File Contoh |
51
+ | Display Name | Resolution (Pixels) | Aspect Ratio | Sample Output File |
52
52
  | :--- | :--- | :--- | :--- |
53
53
  | **6.7" Display** | 1290 x 2796 | 19.5:9 | `6.7_inch_home.png` |
54
54
  | **6.5" Display** | 1242 x 2688 | 19.5:9 | `6.5_inch_home.png` |
55
55
 
56
56
  ### Android (Google Play Store)
57
- | Nama Layar | Resolusi (Piksel) | Rasio Aspek | Output File Contoh |
57
+ | Display Name | Resolution (Pixels) | Aspect Ratio | Sample Output File |
58
58
  | :--- | :--- | :--- | :--- |
59
59
  | **Android Phone** | 1080 x 2400 | 20:9 | `android_phone_home.png` |
60
60
  | **Android Tablet (10")** | 1600 x 2560 | 16:10 | `android_tablet_home.png` |
61
61
 
62
62
  ---
63
63
 
64
- ## šŸš€ Cara Penggunaan Instan (NPX)
64
+ ## šŸš€ Instant Usage (NPX)
65
65
 
66
- Anda tidak perlu menginstal apa pun secara permanen. Cukup jalankan perintah menggunakan `npx`:
66
+ You do not need to install anything permanently. Simply run the command using `npx`:
67
67
 
68
68
  ```powershell
69
- # 1. Jalankan langsung dari server lokal Anda
69
+ # 1. Run directly against your local dev server
70
70
  npx mobile-snap --url http://localhost:4321
71
71
  ```
72
72
 
73
73
  > [!NOTE]
74
- > Jika ini adalah pertama kalinya Anda menjalankan Playwright, Anda mungkin perlu mengunduh browser binaries dengan menjalankan perintah:
74
+ > If this is your first time running Playwright, you may need to download the browser binaries by running:
75
75
  > ```powershell
76
76
  > npx playwright install chromium
77
77
  > ```
78
78
 
79
- Jika Anda ingin menginstalnya secara global di sistem Anda:
79
+ To install it globally on your system:
80
80
  ```powershell
81
81
  npm install -g mobile-snap
82
82
  ```
83
83
 
84
84
  ---
85
85
 
86
- ## šŸ’» Panduan Penggunaan CLI
86
+ ## šŸ’» CLI Usage Guide
87
87
 
88
- Aplikasi ini menerima opsi utama berikut:
88
+ The application accepts the following main options:
89
89
 
90
- | Parameter | Singkatan | Deskripsi | Standar (Default) | Pilihan |
90
+ | Option | Alias | Description | Default | Choices |
91
91
  | :--- | :--- | :--- | :--- | :--- |
92
- | `--url` | `-u` | **(Wajib)** URL server lokal. | - | - |
93
- | `--paths` | `-p` | Jalur/rute halaman yang dipisahkan tanda koma. | `/` | - |
94
- | `--output`| `-o` | Nama direktori tempat menyimpan gambar. | `mobilesnap_output` | - |
95
- | `--platform`| `-l`| Platform target tangkapan layar. | `ios` | `ios`, `android`, `both` |
96
- | `--crawl` | `-c` | Mengaktifkan penelusuran (crawl) otomatis tautan internal di halaman beranda. | `false` | - |
97
- | `--detect-pages` | `-d` | Memindai direktori halaman lokal (`src/pages` atau `pages`) untuk rute statis. | `false` | - |
98
- | `--email` | - | Email untuk autentikasi otomatis. | - | - |
99
- | `--password` | - | Password untuk autentikasi otomatis (disensor di terminal). | - | - |
100
- | `--login-path`| - | Jalur rute ke halaman login. | `/login.html` | - |
101
- | `--html` | - | Otomatis menambahkan akhiran `.html` pada rute statis terdeteksi. | `false` | - |
102
- | `--mockup` | `-m` | Membungkus tangkapan layar dalam bingkai mockup perangkat (iPhone/Android) yang premium dengan status bar dan bayangan transparan. | `false` | - |
103
-
104
- ### Contoh Perintah
105
-
106
- #### 1. Pengambilan Halaman iOS Saja (Default)
92
+ | `--url` | `-u` | **(Required)** Local development server URL. | - | - |
93
+ | `--paths` | `-p` | Comma-separated list of routes to capture. | `/` | - |
94
+ | `--output`| `-o` | Name of the directory to save screenshots. | `mobilesnap_output` | - |
95
+ | `--platform`| `-l`| Target platform for screenshots. | `ios` | `ios`, `android`, `both` |
96
+ | `--crawl` | `-c` | Enable automatic discovery (crawling) of internal links on the home page. | `false` | - |
97
+ | `--detect-pages` | `-d` | Scan local project pages directory (`src/pages` or `pages`) for static routes. | `false` | - |
98
+ | `--email` | - | Email for automatic authentication. | - | - |
99
+ | `--password` | - | Password for automatic authentication (hidden in terminal). | - | - |
100
+ | `--login-path`| - | Route path to the login page. | `/login.html` | - |
101
+ | `--html` | - | Automatically append `.html` extension to detected static routes. | `false` | - |
102
+ | `--mockup` | `-m` | Wrap screenshots in beautiful, premium device mockup frames (iPhone/Android) with status bars and transparent drop shadows. | `false` | - |
103
+
104
+ ### Command Examples
105
+
106
+ #### 1. Capture iOS Only (Default)
107
107
  ```powershell
108
108
  npx mobile-snap --url http://localhost:4321
109
109
  ```
110
110
 
111
- #### 2. Pengambilan Halaman Android Saja dengan Mockup Bingkai Perangkat
111
+ #### 2. Capture Android Only with Device Mockup Frame
112
112
  ```powershell
113
113
  npx mobile-snap --url http://localhost:4321 --platform android --mockup
114
114
  ```
115
115
 
116
- #### 3. Pengambilan Rute Tertentu untuk 2 Platform Sekaligus
117
- Mengambil gambar halaman utama `/` dan halaman `/scan` untuk kedua platform sekaligus ke folder `hasil_store`:
116
+ #### 3. Capture Specific Routes for Both Platforms
117
+ Captures the home page `/` and page `/scan` for both platforms simultaneously into the `hasil_store` directory:
118
118
  ```powershell
119
119
  npx mobile-snap --url http://localhost:4321 --paths "/, /scan" --platform both --output hasil_store
120
120
  ```
121
121
 
122
- #### 4. Auto-Crawl Halaman Web & Login Interaktif dengan Mockup Bingkai Perangkat
123
- Menelusuri semua tautan internal secara otomatis dari beranda dan memotret setiap halaman yang ditemukan dengan bingkai mockup iPhone/Android:
122
+ #### 4. Auto-Crawl Website & Interactive Login with Device Mockup Frame
123
+ Recursively crawls all internal links from the home page and captures each discovered page with a mockup frame:
124
124
  ```powershell
125
125
  npx mobile-snap --url http://localhost:4321 --crawl --platform both --mockup
126
126
  ```
127
127
 
128
-
129
- #### 5. Auto-Detect Rute Proyek Lokal (Astro / Next.js) dengan Auto-Login
130
- Jika Anda berada di dalam root direktori proyek Astro Anda, jalankan perintah ini untuk mendeteksi secara otomatis semua rute halaman statis dari folder `src/pages` dengan login otomatis:
128
+ #### 5. Auto-Detect Local Project Routes (Astro / Next.js) with Auto-Login
129
+ If you are inside the root directory of your Astro/Next.js project, run this command to automatically detect all static page routes from the pages folder and capture them with auto-login:
131
130
  ```powershell
132
131
  npx mobile-snap --url http://localhost:4321 --detect-pages --html --email "user@email.com" --password "rahasia"
133
132
  ```
134
133
 
135
134
  ---
136
135
 
137
- ## šŸ” Autentikasi Otomatis & Interaktif
136
+ ## šŸ” Automatic & Interactive Authentication
138
137
 
139
- MobileSnap secara cerdas membedakan halaman publik dan halaman terproteksi (yang membutuhkan login) berdasarkan pengalihan client-side ke rute login.
138
+ MobileSnap intelligently distinguishes between public pages and protected pages (requiring authentication) based on client-side redirects to the login route.
140
139
 
141
- Jika terdeteksi rute yang memerlukan login, MobileSnap akan:
142
- 1. **Meminta Kredensial Secara Interaktif**: Jika opsi `--email` dan/atau `--password` tidak diberikan lewat CLI, sistem akan menanyakan email dan password secara interaktif di terminal dengan sensor password otomatis demi keamanan.
143
- 2. **Auto-Login**: MobileSnap akan melakukan proses sign-in sebelum mengambil tangkapan layar untuk semua rute terproteksi.
144
- 3. **Crawl Pasca-Login**: Jika opsi `--crawl` aktif, MobileSnap juga akan menjelajahi menu dan tautan internal yang baru muncul di dashboard pasca-login.
140
+ If a route requiring login is detected, MobileSnap will:
141
+ 1. **Prompt for Credentials Interactively**: If `--email` and/or `--password` options are not supplied via the CLI, the tool will prompt for them in the terminal, masking the password input for security.
142
+ 2. **Auto-Login**: MobileSnap performs the sign-in sequence before capturing screenshots for all protected routes.
143
+ 3. **Post-Login Crawl**: If the `--crawl` option is enabled, MobileSnap will also discover and crawl internal links that only become visible in the dashboard post-authentication.
145
144
 
146
145
  ---
147
146
 
148
- ## ā„¹ļø Bantuan Perintah (`--help`)
147
+ ## ā„¹ļø Command Help (`--help`)
149
148
 
150
- Anda selalu dapat memanggil opsi bantuan langsung dari terminal dengan menjalankan:
149
+ You can display the help information directly from the terminal by running:
151
150
 
152
151
  ```powershell
153
152
  npx mobile-snap --help
154
153
  ```
155
154
 
156
- Output bantuan resmi:
155
+ Official help output:
157
156
  ```text
158
157
  Usage: mobile-snap [options]
159
158
 
@@ -161,18 +160,24 @@ Usage: mobile-snap [options]
161
160
 
162
161
  Options:
163
162
  -V, --version output the version number
164
- -u, --url <url> Base URL of the local development server (e.g. localhost:3000)
165
- -p, --paths <paths> Comma-separated list of routes to capture (default: "/")
166
- -o, --output <output> Output directory to save screenshots (default: "mobilesnap_output")
167
- -l, --platform <platform> Target platform: "ios", "android", or "both" (default: "ios")
168
- -c, --crawl Discover and screenshot all internal links automatically (default: false)
169
- -d, --detect-pages Scan local project pages directory (src/pages or pages) for static routes (default: false)
163
+ -u, --url <url> Base URL of the local development server (e.g.
164
+ localhost:3000)
165
+ -p, --paths <paths> Comma-separated list of routes to capture
166
+ (default: "/")
167
+ -o, --output <output> Output directory to save screenshots (default:
168
+ "mobilesnap_output")
169
+ -l, --platform <platform> Target platform: "ios", "android", or "both"
170
+ (default: "ios")
171
+ -c, --crawl Discover and screenshot all internal links
172
+ automatically (default: false)
173
+ -d, --detect-pages Scan local project pages directory (src/pages or
174
+ pages) for static routes (default: false)
170
175
  --email <email> Email for automatic login authentication
171
176
  --password <password> Password for automatic login authentication
172
177
  --login-path <path> Path to the login page (default: "/login.html")
173
- --html Auto append .html extension to detected routes (default: false)
174
- -m, --mockup Wrap screenshots in a beautiful iPhone/Android device mockup frame (default: false)
178
+ --html Auto append .html extension to detected routes
179
+ (default: false)
180
+ -m, --mockup Wrap screenshots in a beautiful iPhone/Android
181
+ device mockup frame (default: false)
175
182
  -h, --help display help for command
176
183
  ```
177
-
178
-
package/bin/cli.js CHANGED
@@ -8,7 +8,7 @@ import fs from 'fs';
8
8
  import path from 'path';
9
9
  import readline from 'readline';
10
10
 
11
- // Device configurations dengan ukuran logis (CSS pixels) dan scale factor untuk resolusi fisik presisi
11
+ // Device configurations with logical dimensions (CSS pixels) and device scale factor for precise physical resolution
12
12
  const DEVICE_CONFIGS = {
13
13
  ios: {
14
14
  devices: {
@@ -68,7 +68,7 @@ function safeFilename(route) {
68
68
  return cleanPath.replace(/[^a-zA-Z0-9_\-]/g, '_').replace(/_+/g, '_').replace(/^_+|_+$/g, '');
69
69
  }
70
70
 
71
- // Fungsi pembantu untuk memindai folder src/pages atau pages (Astro/Next.js)
71
+ // Helper function to scan the src/pages or pages directory (for Astro, Next.js, etc.)
72
72
  function detectLocalPages(dir = process.cwd()) {
73
73
  const pagesDirs = [
74
74
  path.join(dir, 'src', 'pages'),
@@ -100,11 +100,11 @@ function detectLocalPages(dir = process.cwd()) {
100
100
  if (name === 'index') {
101
101
  route = baseRoute || '/';
102
102
  }
103
- // Abaikan rute dinamis yang mengandung [ atau ]
103
+ // Ignore dynamic routes containing [ or ]
104
104
  if (!route.includes('[') && !route.includes(']')) {
105
- // Pastikan diawali /
105
+ // Ensure it starts with /
106
106
  let finalRoute = '/' + route.replace(/^\/+/, '');
107
- // Abaikan rute API (server endpoint)
107
+ // Ignore API routes (server endpoints)
108
108
  if (!finalRoute.startsWith('/api/')) {
109
109
  routes.push(finalRoute);
110
110
  }
@@ -118,7 +118,7 @@ function detectLocalPages(dir = process.cwd()) {
118
118
  return [...new Set(routes)];
119
119
  }
120
120
 
121
- // Fungsi pembantu untuk melakukan crawling internal links dari halaman utama
121
+ // Helper function to crawl internal links starting from the home page
122
122
  async function analyzeRoutes(browser, baseUrl, initialPaths, email, password, loginPath, addHtml, crawl) {
123
123
  const normLoginPath = '/' + loginPath.replace(/^\/+/, '');
124
124
  const publicRoutes = new Set();
@@ -142,10 +142,10 @@ async function analyzeRoutes(browser, baseUrl, initialPaths, email, password, lo
142
142
  console.error(pc.red(` [Crawler Error] ${err.message}`));
143
143
  });
144
144
 
145
- const nonAuthSpinner = ora('Menganalisis rute publik dan mendeteksi kebutuhan autentikasi...').start();
145
+ const nonAuthSpinner = ora('Analyzing public routes and detecting authentication requirements...').start();
146
146
  const routesToCheck = Array.from(allDetectedRoutes);
147
147
 
148
- // Jika daftar awal kosong, tambahkan root '/'
148
+ // If the initial path list is empty, default to the root path '/'
149
149
  if (routesToCheck.length === 0) {
150
150
  routesToCheck.push('/');
151
151
  allDetectedRoutes.add('/');
@@ -163,7 +163,7 @@ async function analyzeRoutes(browser, baseUrl, initialPaths, email, password, lo
163
163
  await page.goto(targetUrl, { timeout: 15000 });
164
164
  await page.waitForLoadState('networkidle', { timeout: 3000 }).catch(() => {});
165
165
 
166
- // Tunggu client-side redirect (seperti splash screen ke login) selesai secara dinamis
166
+ // Wait dynamically for client-side redirects (such as splash screen to login) to complete
167
167
  if (cleanRoute === '/' || cleanRoute.includes('splash')) {
168
168
  await page.waitForURL(u => {
169
169
  const pathname = u.pathname;
@@ -181,12 +181,12 @@ async function analyzeRoutes(browser, baseUrl, initialPaths, email, password, lo
181
181
  finalPath = finalUrl;
182
182
  }
183
183
 
184
- // Bersihkan finalPath agar berakhiran .html jika flag aktif
184
+ // Normalize finalPath to end with .html if the flag is active
185
185
  if (addHtml && finalPath !== '/' && !finalPath.endsWith('.html') && !/\.[a-z0-9]+$/i.test(finalPath)) {
186
186
  finalPath += '.html';
187
187
  }
188
188
 
189
- // Cek apakah ada form login atau input kredensial di DOM halaman saat ini
189
+ // Check if a login form or credential inputs are present in the current page DOM
190
190
  const hasLoginForm = await page.evaluate(() => {
191
191
  const hasUser = !!document.querySelector('#username, input[type="email"], input[name="username"], input[name="login"]');
192
192
  const hasPass = !!document.querySelector('#password, input[type="password"], input[name="password"]');
@@ -217,7 +217,7 @@ async function analyzeRoutes(browser, baseUrl, initialPaths, email, password, lo
217
217
  publicRoutes.add(cleanRoute);
218
218
  }
219
219
 
220
- // Selalu rayapi link publik jika crawl aktif dan halaman saat ini tidak memerlukan login (tidak ter-redirect)
220
+ // Always crawl public links if crawling is active and the current page does not require authentication
221
221
  if (crawl && !authRoutes.has(cleanRoute)) {
222
222
  const hrefs = await page.evaluate(() => {
223
223
  return Array.from(document.querySelectorAll('a'))
@@ -253,32 +253,32 @@ async function analyzeRoutes(browser, baseUrl, initialPaths, email, password, lo
253
253
  }
254
254
  }
255
255
  } catch (err) {
256
- ora().warn(pc.yellow(`Gagal menganalisis rute ${cleanRoute}: ${err.message}`));
256
+ ora().warn(pc.yellow(`Failed to analyze route ${cleanRoute}: ${err.message}`));
257
257
  publicRoutes.add(cleanRoute);
258
258
  }
259
259
  }
260
260
 
261
261
  await context.close();
262
- nonAuthSpinner.succeed(`Analisis awal selesai. Terdeteksi ${publicRoutes.size} rute publik dan ${authRoutes.size} rute membutuhkan autentikasi.`);
262
+ nonAuthSpinner.succeed(`Initial analysis complete. Detected ${publicRoutes.size} public routes and ${authRoutes.size} routes requiring authentication.`);
263
263
 
264
- // 2. Tanya Kredensial secara interaktif jika ada rute auth dan email/pass kosong
264
+ // 2. Prompt for credentials interactively if there are authenticated routes and email/password are empty
265
265
  let finalEmail = email;
266
266
  let finalPassword = password;
267
267
 
268
268
  if (authRoutes.size > 0 && (!finalEmail || !finalPassword)) {
269
- console.log(pc.yellow(`\nšŸ”‘ Mendeteksi ${authRoutes.size} halaman yang membutuhkan login.`));
269
+ console.log(pc.yellow(`\nšŸ”‘ Detected ${authRoutes.size} pages requiring login.`));
270
270
  if (!finalEmail) {
271
- finalEmail = await promptUser('šŸ‘‰ Masukkan Email/Username: ');
271
+ finalEmail = await promptUser('šŸ‘‰ Enter Email/Username: ');
272
272
  }
273
273
  if (!finalPassword) {
274
- finalPassword = await promptUser('šŸ‘‰ Masukkan Password (input tersembunyi): ', true);
275
- console.log(''); // baris baru setelah menekan enter
274
+ finalPassword = await promptUser('šŸ‘‰ Enter Password (hidden input): ', true);
275
+ console.log(''); // new line after pressing enter
276
276
  }
277
277
  }
278
278
 
279
- // 3. Crawl Fase Kedua (Login) untuk merayapi halaman dashboard internal
279
+ // 3. Phase Two Crawl (Post-Login) to discover internal dashboard pages
280
280
  if (authRoutes.size > 0 && finalEmail && finalPassword) {
281
- const authCrawlSpinner = ora('Melakukan login dan memindai halaman internal...').start();
281
+ const authCrawlSpinner = ora('Performing login and crawling authenticated internal pages...').start();
282
282
  const authContext = await browser.newContext();
283
283
  const authPage = await authContext.newPage();
284
284
 
@@ -333,7 +333,7 @@ async function analyzeRoutes(browser, baseUrl, initialPaths, email, password, lo
333
333
  }
334
334
 
335
335
  if (!loginSuccess) {
336
- throw new Error(loginError || 'Timeout: URL halaman tidak berubah dari halaman login setelah 10 detik.');
336
+ throw new Error(loginError || 'Timeout: Page URL did not change from the login page after 10 seconds.');
337
337
  }
338
338
 
339
339
  if (crawl) {
@@ -378,13 +378,13 @@ async function analyzeRoutes(browser, baseUrl, initialPaths, email, password, lo
378
378
  } catch (e) {}
379
379
  }
380
380
  } catch (routeErr) {
381
- // Abaikan jika rute tertentu gagal muat
381
+ // Ignore if a specific route fails to load
382
382
  }
383
383
  }
384
384
  }
385
- authCrawlSpinner.succeed(`Crawl pasca-login selesai. Menemukan total ${authRoutes.size} rute terotentikasi.`);
385
+ authCrawlSpinner.succeed(`Post-login crawl complete. Found a total of ${authRoutes.size} authenticated routes.`);
386
386
  } catch (err) {
387
- authCrawlSpinner.fail(`Gagal merayapi halaman terotentikasi: ${err.message}`);
387
+ authCrawlSpinner.fail(`Failed to crawl authenticated pages: ${err.message}`);
388
388
  } finally {
389
389
  await authContext.close();
390
390
  }
@@ -681,14 +681,14 @@ async function captureScreenshots(url, paths, outputDir, platform, crawl, detect
681
681
 
682
682
  let finalPaths = [...paths];
683
683
 
684
- // 1. Auto-detect halaman lokal jika diaktifkan
684
+ // 1. Auto-detect local pages if enabled
685
685
  if (detectPages) {
686
686
  const localRoutes = detectLocalPages();
687
687
  if (localRoutes && localRoutes.length > 0) {
688
- console.log(pc.green(`šŸ” Mendeteksi folder halaman lokal. Menambahkan ${localRoutes.length} rute statis.`));
688
+ console.log(pc.green(`šŸ” Detected local pages folder. Adding ${localRoutes.length} static routes.`));
689
689
  finalPaths = [...new Set([...finalPaths, ...localRoutes])];
690
690
  } else {
691
- console.log(pc.yellow(`⚠ Folder 'src/pages' atau 'pages' tidak ditemukan di direktori saat ini.`));
691
+ console.log(pc.yellow(`⚠ Folder 'src/pages' or 'pages' not found in the current directory.`));
692
692
  }
693
693
  }
694
694
 
@@ -697,12 +697,12 @@ async function captureScreenshots(url, paths, outputDir, platform, crawl, detect
697
697
  console.log(`Platform(s): ${pc.cyan(targetPlatforms.join(', ').toUpperCase())}`);
698
698
  console.log(`Output Directory: ${pc.cyan(path.resolve(outputDir))}\n`);
699
699
 
700
- // Pastikan direktori output ada
700
+ // Ensure the output directory exists
701
701
  if (!fs.existsSync(outputDir)) {
702
702
  fs.mkdirSync(outputDir, { recursive: true });
703
703
  }
704
704
 
705
- // Luncurkan browser
705
+ // Launch browser
706
706
  let browser;
707
707
  const launchSpinner = ora('Launching Chromium browser...').start();
708
708
  try {
@@ -714,33 +714,33 @@ async function captureScreenshots(url, paths, outputDir, platform, crawl, detect
714
714
  } catch (err) {
715
715
  launchSpinner.fail(pc.red('Failed to launch Chromium browser'));
716
716
  console.error(pc.red(err.message));
717
- console.log(pc.yellow('\nšŸ’” Tips: Jalankan "npx playwright install chromium" untuk mengunduh browser binaries.'));
717
+ console.log(pc.yellow('\nšŸ’” Tip: Run "npx playwright install chromium" to download browser binaries.'));
718
718
  process.exit(1);
719
719
  }
720
720
 
721
- // Panggil analyzeRoutes untuk memisahkan rute publik dan terotentikasi
721
+ // Call analyzeRoutes to separate public and authenticated routes
722
722
  let result;
723
723
  try {
724
724
  result = await analyzeRoutes(browser, url, finalPaths, email, password, loginPath, addHtml, crawl);
725
725
  } catch (err) {
726
- console.error(pc.red(`Gagal menganalisis rute: ${err.message}`));
726
+ console.error(pc.red(`Failed to analyze routes: ${err.message}`));
727
727
  await browser.close();
728
728
  process.exit(1);
729
729
  }
730
730
 
731
731
  const { publicRoutes, authRoutes, email: finalEmail, password: finalPassword } = result;
732
732
 
733
- console.log(pc.bold(`\nTerdeteksi Rute Publik (${publicRoutes.length}):`));
733
+ console.log(pc.bold(`\nDetected Public Routes (${publicRoutes.length}):`));
734
734
  publicRoutes.forEach(p => console.log(` - ${pc.green(p)}`));
735
735
 
736
736
  if (authRoutes.length > 0) {
737
- console.log(pc.bold(`\nTerdeteksi Rute Membutuhkan Autentikasi (${authRoutes.length}):`));
737
+ console.log(pc.bold(`\nDetected Routes Requiring Authentication (${authRoutes.length}):`));
738
738
  authRoutes.forEach(p => console.log(` - ${pc.cyan(p)}`));
739
739
  }
740
740
  console.log('');
741
741
 
742
742
  if (publicRoutes.length === 0 && authRoutes.length === 0) {
743
- console.log(pc.yellow('Tidak ada rute yang ditemukan untuk dipotret.'));
743
+ console.log(pc.yellow('No routes were found to capture.'));
744
744
  await browser.close();
745
745
  return;
746
746
  }
@@ -816,9 +816,9 @@ async function captureScreenshots(url, paths, outputDir, platform, crawl, detect
816
816
  console.error(pc.red(` [Browser Error] ${err.message}`));
817
817
  });
818
818
 
819
- // --- Tahap 1: Memotret Halaman Publik (Tanpa Login) ---
819
+ // --- Phase 1: Capturing Public Pages (No Login) ---
820
820
  if (publicRoutes.length > 0) {
821
- console.log(` šŸ“ø Memotret halaman publik...`);
821
+ console.log(` šŸ“ø Capturing public pages...`);
822
822
  for (const route of publicRoutes) {
823
823
  const targetUrl = `${url}${route}`;
824
824
  const nameSnippet = safeFilename(route);
@@ -841,7 +841,7 @@ async function captureScreenshots(url, paths, outputDir, platform, crawl, detect
841
841
  }
842
842
  }
843
843
 
844
- // --- Tahap 2: Memotret Halaman Terotentikasi ---
844
+ // --- Phase 2: Capturing Authenticated Pages ---
845
845
  if (authRoutes.length > 0) {
846
846
  if (finalEmail && finalPassword) {
847
847
  const loginSpinner = ora(` Logging in to session for ${deviceName}...`).start();
@@ -856,7 +856,7 @@ async function captureScreenshots(url, paths, outputDir, platform, crawl, detect
856
856
  const submitBtn = await page.locator('button[type="submit"], button.save-btn').first();
857
857
  await submitBtn.click();
858
858
 
859
- // Tunggu hingga login berhasil (URL berubah) atau ada error message
859
+ // Wait for login success (URL change) or an error message to appear
860
860
  let loginSuccess = false;
861
861
  let loginError = '';
862
862
  for (let i = 0; i < 40; i++) {
@@ -882,12 +882,12 @@ async function captureScreenshots(url, paths, outputDir, platform, crawl, detect
882
882
  }
883
883
 
884
884
  if (!loginSuccess) {
885
- throw new Error(loginError || 'Timeout: URL halaman tidak berubah dari halaman login setelah 10 detik.');
885
+ throw new Error(loginError || 'Timeout: Page URL did not change from the login page after 10 seconds.');
886
886
  }
887
887
 
888
888
  loginSpinner.succeed(` Logged in successfully for ${deviceName}`);
889
889
 
890
- console.log(` šŸ“ø Memotret halaman terotentikasi...`);
890
+ console.log(` šŸ“ø Capturing authenticated pages...`);
891
891
  for (const route of authRoutes) {
892
892
  const targetUrl = `${url}${route}`;
893
893
  const nameSnippet = safeFilename(route);
@@ -910,7 +910,7 @@ async function captureScreenshots(url, paths, outputDir, platform, crawl, detect
910
910
  }
911
911
  } catch (loginErr) {
912
912
  loginSpinner.fail(pc.red(` Auto-login failed for ${deviceName}: ${loginErr.message}`));
913
- console.log(pc.yellow(` šŸ’” Melanjutkan capture auth routes tanpa login (mungkin ter-redirect ke login/splash).`));
913
+ console.log(pc.yellow(` šŸ’” Continuing to capture authenticated routes without login (may be redirected to login/splash).`));
914
914
 
915
915
  for (const route of authRoutes) {
916
916
  const targetUrl = `${url}${route}`;
@@ -934,7 +934,7 @@ async function captureScreenshots(url, paths, outputDir, platform, crawl, detect
934
934
  }
935
935
  }
936
936
  } else {
937
- console.log(pc.yellow(` ⚠ Skip login (kredensial kosong). Memotret auth routes tanpa login.`));
937
+ console.log(pc.yellow(` ⚠ Skipping login (empty credentials). Capturing authenticated routes without login.`));
938
938
  for (const route of authRoutes) {
939
939
  const targetUrl = `${url}${route}`;
940
940
  const nameSnippet = safeFilename(route);
@@ -963,13 +963,13 @@ async function captureScreenshots(url, paths, outputDir, platform, crawl, detect
963
963
  }
964
964
 
965
965
  await browser.close();
966
- console.log(pc.bold(pc.green(`\nšŸŽ‰ Selesai! Semua tangkapan layar disimpan di '${outputDir}'.`)));
966
+ console.log(pc.bold(pc.green(`\nšŸŽ‰ Finished! All screenshots successfully saved in '${outputDir}'.`)));
967
967
  }
968
968
 
969
969
  program
970
970
  .name('mobile-snap')
971
971
  .description('⚔ MobileSnap CLI: Automate App Store & Google Play Store screenshots')
972
- .version('1.0.3')
972
+ .version('1.0.5')
973
973
  .requiredOption('-u, --url <url>', 'Base URL of the local development server (e.g. localhost:3000)')
974
974
  .option('-p, --paths <paths>', 'Comma-separated list of routes to capture', '/')
975
975
  .option('-o, --output <output>', 'Output directory to save screenshots', 'mobilesnap_output')
@@ -989,7 +989,7 @@ program
989
989
 
990
990
  const platformVal = options.platform.toLowerCase();
991
991
  if (!['ios', 'android', 'both'].includes(platformVal)) {
992
- console.error(pc.red(`Error: Platform '${options.platform}' tidak valid. Pilih antara 'ios', 'android', atau 'both'.`));
992
+ console.error(pc.red(`Error: Invalid platform '${options.platform}'. Choose 'ios', 'android', or 'both'.`));
993
993
  process.exit(1);
994
994
  }
995
995
 
@@ -1006,7 +1006,7 @@ program
1006
1006
  options.html,
1007
1007
  options.mockup
1008
1008
  ).catch(err => {
1009
- console.error(pc.red(`Terjadi kesalahan tidak terduga: ${err.message}`));
1009
+ console.error(pc.red(`An unexpected error occurred: ${err.message}`));
1010
1010
  process.exit(1);
1011
1011
  });
1012
1012
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobile-snap",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Automate pixel-precise App Store & Google Play screenshots from a local web server",
5
5
  "type": "module",
6
6
  "main": "bin/cli.js",