device-shots 0.1.0 → 0.2.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 (3) hide show
  1. package/README.md +180 -0
  2. package/dist/index.js +174 -49
  3. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,180 @@
1
+ # device-shots
2
+
3
+ A CLI tool that captures screenshots from running iOS simulators and Android emulators, then optionally frames iOS screenshots with device bezels. Built for developers who need store-ready screenshots without manual work.
4
+
5
+ ## What it does
6
+
7
+ 1. **Discovers devices** — Finds all running iOS simulators and Android emulators that have your app installed
8
+ 2. **Cleans up status bars** — Sets a uniform status bar (9:41, full battery, full signal) on all devices before capturing
9
+ 3. **Captures screenshots** — Takes screenshots from every discovered device in one go
10
+ 4. **Transparent Android status bar** — Automatically makes the Android status bar area transparent using ImageMagick
11
+ 5. **Frames iOS screenshots** — Wraps iOS screenshots in device bezels (iPhone/iPad frames) using [device-frames-core](https://pypi.org/project/device-frames-core/)
12
+ 6. **Organizes by screen size** — Saves screenshots into store-aligned size buckets (e.g. `6.9`, `6.7`, `phone`) instead of device names
13
+
14
+ ## Use cases
15
+
16
+ - Generating App Store and Play Store screenshots across multiple device sizes at once
17
+ - Keeping consistent, reproducible screenshot sets for your app listing
18
+ - Automating screenshot capture in CI/CD pipelines
19
+ - Quickly re-capturing screenshots after UI changes
20
+
21
+ ## Prerequisites
22
+
23
+ ### Required
24
+
25
+ - **Node.js 18+**
26
+ - **Xcode Command Line Tools** (for iOS) — provides `xcrun simctl`
27
+ - **Android SDK** (for Android) — provides `adb`. The tool looks for it at `$ANDROID_HOME/platform-tools/adb` or `~/Library/Android/sdk/platform-tools/adb`
28
+
29
+ ### Optional
30
+
31
+ - **ImageMagick** — needed to make Android status bars transparent. Install with `brew install imagemagick`
32
+ - **Python 3** — needed for iOS screenshot framing. The tool auto-creates a virtual environment at `~/.device-shots/.venv` and installs `device-frames-core` and `Pillow` automatically on first use
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ npm install -g device-shots
38
+ ```
39
+
40
+ Or run directly with `npx`:
41
+
42
+ ```bash
43
+ npx device-shots capture --bundle-id com.example.myapp
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ ### Capture screenshots
49
+
50
+ ```bash
51
+ # Interactive — prompts for screenshot name
52
+ device-shots capture --bundle-id com.example.myapp
53
+
54
+ # Non-interactive — provide name directly
55
+ device-shots capture dashboard --bundle-id com.example.myapp
56
+
57
+ # iOS only, custom output directory
58
+ device-shots capture home -b com.example.myapp -p ios -o ./.store-assets
59
+
60
+ # Skip framing
61
+ device-shots capture login -b com.example.myapp --no-frame
62
+
63
+ # Custom status bar time
64
+ device-shots capture checkout -b com.example.myapp --time "10:30"
65
+ ```
66
+
67
+ ### Frame existing screenshots
68
+
69
+ ```bash
70
+ # Frame all unframed iOS screenshots in ./.screenshots
71
+ device-shots frame
72
+
73
+ # Frame from a specific directory
74
+ device-shots frame ./.my-screenshots
75
+
76
+ # Re-frame everything (overwrite existing framed images)
77
+ device-shots frame --force
78
+ ```
79
+
80
+ ### Initialize config
81
+
82
+ ```bash
83
+ device-shots init
84
+ ```
85
+
86
+ Creates a `.device-shotsrc.json` in the current directory so you don't have to pass `--bundle-id` every time:
87
+
88
+ ```json
89
+ {
90
+ "bundleId": "com.example.myapp",
91
+ "output": "./.screenshots",
92
+ "platform": "both",
93
+ "time": "9:41",
94
+ "frame": true
95
+ }
96
+ ```
97
+
98
+ ## Config file
99
+
100
+ The tool uses [cosmiconfig](https://github.com/cosmiconfig/cosmiconfig), so you can configure it in any of these ways:
101
+
102
+ - `.device-shotsrc.json`
103
+ - `.device-shotsrc.yaml`
104
+ - `.device-shotsrc.js`
105
+ - `device-shots.config.js`
106
+ - `"device-shots"` key in `package.json`
107
+
108
+ ## Output structure
109
+
110
+ Screenshots are organized by platform and screen size bucket, matching App Store and Play Store requirements:
111
+
112
+ ```
113
+ .screenshots/
114
+ ├── ios/
115
+ │ ├── 6.9/ # iPhone 16 Pro Max
116
+ │ │ ├── dashboard.png
117
+ │ │ ├── dashboard_framed.png
118
+ │ │ ├── settings.png
119
+ │ │ └── settings_framed.png
120
+ │ ├── 6.7/ # iPhone 16 Plus, 15 Pro Max
121
+ │ │ ├── dashboard.png
122
+ │ │ └── dashboard_framed.png
123
+ │ └── 6.3/ # iPhone 16 Pro
124
+ │ └── ...
125
+ ├── android/
126
+ │ └── phone/ # Pixel 9 Pro, etc.
127
+ │ ├── dashboard.png
128
+ │ └── settings.png
129
+ └── metadata.json
130
+ ```
131
+
132
+ ### Screen size buckets
133
+
134
+ **iOS** — Maps to App Store Connect display size categories:
135
+
136
+ | Bucket | Devices |
137
+ |--------|---------|
138
+ | `6.9` | iPhone 16 Pro Max |
139
+ | `6.7` | iPhone 16 Plus, 15 Pro Max, 15 Plus, 14 Pro Max |
140
+ | `6.5` | iPhone 14 Plus, 13 Pro Max, 12 Pro Max, 11 Pro Max |
141
+ | `6.3` | iPhone 16 Pro |
142
+ | `6.1` | iPhone 16, 15, 15 Pro, 14, 14 Pro, 13, 13 Pro |
143
+ | `5.5` | iPhone 8 Plus, 7 Plus |
144
+ | `13` | iPad Pro 13" |
145
+ | `11` | iPad Pro 11", iPad Air |
146
+
147
+ **Android** — Categorized by form factor:
148
+
149
+ | Bucket | Criteria |
150
+ |--------|----------|
151
+ | `phone` | Shorter screen dimension < 1200px |
152
+ | `tablet-7` | Shorter dimension 1200–1799px |
153
+ | `tablet-10` | Shorter dimension 1800px+ |
154
+
155
+ ### metadata.json
156
+
157
+ Tracks which physical device was used for each size bucket:
158
+
159
+ ```json
160
+ {
161
+ "ios": {
162
+ "6.9": {
163
+ "device": "iPhone 16 Pro Max",
164
+ "id": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
165
+ "resolution": "1320x2868"
166
+ }
167
+ },
168
+ "android": {
169
+ "phone": {
170
+ "device": "Pixel 9 Pro",
171
+ "id": "emulator-5554",
172
+ "resolution": "1080x2340"
173
+ }
174
+ }
175
+ }
176
+ ```
177
+
178
+ ## License
179
+
180
+ MIT
package/dist/index.js CHANGED
@@ -2,12 +2,19 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import { writeFileSync, existsSync as existsSync5 } from "fs";
5
+ import { writeFileSync as writeFileSync2, existsSync as existsSync5 } from "fs";
6
6
  import pc3 from "picocolors";
7
7
  import prompts2 from "prompts";
8
8
 
9
9
  // src/commands/capture.ts
10
- import { existsSync as existsSync4, mkdirSync, readdirSync as readdirSync2, renameSync } from "fs";
10
+ import {
11
+ existsSync as existsSync4,
12
+ mkdirSync,
13
+ readdirSync as readdirSync2,
14
+ renameSync,
15
+ readFileSync,
16
+ writeFileSync
17
+ } from "fs";
11
18
  import { join as join4 } from "path";
12
19
  import { tmpdir } from "os";
13
20
  import { mkdtempSync } from "fs";
@@ -20,7 +27,7 @@ import { cosmiconfig } from "cosmiconfig";
20
27
  var MODULE_NAME = "device-shots";
21
28
  var DEFAULTS = {
22
29
  bundleId: "",
23
- output: "./screenshots",
30
+ output: "./.screenshots",
24
31
  platform: "both",
25
32
  time: "9:41",
26
33
  frame: true
@@ -40,7 +47,7 @@ function createDefaultConfig(bundleId) {
40
47
  return JSON.stringify(
41
48
  {
42
49
  bundleId,
43
- output: "./screenshots",
50
+ output: "./.screenshots",
44
51
  platform: "both",
45
52
  time: "9:41",
46
53
  frame: true
@@ -87,6 +94,50 @@ async function runOrFail(cmd, args, options) {
87
94
  return result.stdout ?? "";
88
95
  }
89
96
 
97
+ // src/devices/screen-sizes.ts
98
+ var IOS_RESOLUTION_MAP = {
99
+ // 6.9" — iPhone 16 Pro Max
100
+ "1320x2868": "6.9",
101
+ // 6.7" — iPhone 16 Plus, 15 Pro Max, 15 Plus, 14 Pro Max
102
+ "1290x2796": "6.7",
103
+ // 6.5" — iPhone 14 Plus, 13 Pro Max, 12 Pro Max, 11 Pro Max
104
+ "1284x2778": "6.5",
105
+ "1242x2688": "6.5",
106
+ // 6.3" — iPhone 16 Pro
107
+ "1206x2622": "6.3",
108
+ // 6.1" — iPhone 16, 15, 15 Pro, 14, 14 Pro, 13, 13 Pro, 12, 12 Pro
109
+ "1179x2556": "6.1",
110
+ "1170x2532": "6.1",
111
+ // 5.8" — iPhone X, XS, 11 Pro
112
+ "1125x2436": "5.8",
113
+ // 5.5" — iPhone 8 Plus, 7 Plus, 6s Plus
114
+ "1242x2208": "5.5",
115
+ // 4.7" — iPhone SE (3rd/2nd), iPhone 8, 7, 6s
116
+ "750x1334": "4.7",
117
+ // iPad 13" — iPad Pro 13" (M4), iPad Pro 12.9" (older)
118
+ "2064x2752": "13",
119
+ "2048x2732": "13",
120
+ // iPad 11" — iPad Pro 11", iPad Air
121
+ "1668x2388": "11",
122
+ "1668x2224": "11",
123
+ "1640x2360": "11",
124
+ // iPad 10.9" — iPad Air (5th), iPad (10th)
125
+ "2360x1640": "11",
126
+ // iPad mini
127
+ "1488x2266": "8.3"
128
+ };
129
+ function getIosScreenSize(width, height) {
130
+ const w = Math.min(width, height);
131
+ const h = Math.max(width, height);
132
+ return IOS_RESOLUTION_MAP[`${w}x${h}`] ?? `${w}x${h}`;
133
+ }
134
+ function getAndroidScreenSize(width, height) {
135
+ const shorter = Math.min(width, height);
136
+ if (shorter >= 1800) return "tablet-10";
137
+ if (shorter >= 1200) return "tablet-7";
138
+ return "phone";
139
+ }
140
+
90
141
  // src/devices/ios.ts
91
142
  function sanitizeName(name) {
92
143
  return name.replace(/[ (),]/g, "_").replace(/[^A-Za-z0-9_-]/g, "");
@@ -115,17 +166,63 @@ async function discoverIosDevices(bundleId) {
115
166
  bundleId
116
167
  ]);
117
168
  if (container) {
169
+ const resolution = await getResolutionViaTestCapture(d.udid);
170
+ const screenSize = resolution ? getIosScreenSize(resolution.width, resolution.height) : sanitizeName(d.name);
118
171
  devices.push({
119
172
  platform: "ios",
120
173
  safeName: sanitizeName(d.name),
121
174
  captureId: d.udid,
122
- displayName: d.name
175
+ displayName: d.name,
176
+ screenSize,
177
+ resolution: resolution ?? { width: 0, height: 0 }
123
178
  });
124
179
  }
125
180
  }
126
181
  }
127
182
  return devices;
128
183
  }
184
+ async function getResolutionViaTestCapture(udid) {
185
+ try {
186
+ const tmpPath = `/tmp/device-shots-probe-${udid}.png`;
187
+ await runOrFail("xcrun", ["simctl", "io", udid, "screenshot", tmpPath]);
188
+ if (await commandExists("magick")) {
189
+ const { stdout } = await run("magick", [
190
+ "identify",
191
+ "-format",
192
+ "%wx%h",
193
+ tmpPath
194
+ ]);
195
+ const match = stdout.match(/(\d+)x(\d+)/);
196
+ await run("rm", ["-f", tmpPath]);
197
+ if (match) {
198
+ return {
199
+ width: parseInt(match[1], 10),
200
+ height: parseInt(match[2], 10)
201
+ };
202
+ }
203
+ } else {
204
+ const { stdout } = await run("sips", [
205
+ "-g",
206
+ "pixelWidth",
207
+ "-g",
208
+ "pixelHeight",
209
+ tmpPath
210
+ ]);
211
+ await run("rm", ["-f", tmpPath]);
212
+ const wMatch = stdout.match(/pixelWidth:\s*(\d+)/);
213
+ const hMatch = stdout.match(/pixelHeight:\s*(\d+)/);
214
+ if (wMatch && hMatch) {
215
+ return {
216
+ width: parseInt(wMatch[1], 10),
217
+ height: parseInt(hMatch[1], 10)
218
+ };
219
+ }
220
+ }
221
+ await run("rm", ["-f", tmpPath]);
222
+ } catch {
223
+ }
224
+ return null;
225
+ }
129
226
  async function setIosStatusBar(time) {
130
227
  if (!await commandExists("xcrun")) return;
131
228
  await run("xcrun", [
@@ -164,6 +261,16 @@ async function captureIosScreenshot(udid, outputPath) {
164
261
  function sanitizeName2(name) {
165
262
  return name.replace(/[ (),]/g, "_").replace(/[^A-Za-z0-9_-]/g, "");
166
263
  }
264
+ async function getAndroidResolution(serial) {
265
+ const adb = getAdbPath();
266
+ const { stdout } = await run(adb, ["-s", serial, "shell", "wm", "size"]);
267
+ const match = stdout.match(/(\d+)x(\d+)/);
268
+ if (!match) return null;
269
+ return {
270
+ width: parseInt(match[1], 10),
271
+ height: parseInt(match[2], 10)
272
+ };
273
+ }
167
274
  async function discoverAndroidDevices(bundleId) {
168
275
  const adb = getAdbPath();
169
276
  const { stdout } = await run(adb, ["devices"]);
@@ -203,11 +310,15 @@ async function discoverAndroidDevices(bundleId) {
203
310
  avdName = model.trim();
204
311
  }
205
312
  const safeName = sanitizeName2(avdName) || serial;
313
+ const resolution = await getAndroidResolution(serial);
314
+ const screenSize = resolution ? getAndroidScreenSize(resolution.width, resolution.height) : "phone";
206
315
  devices.push({
207
316
  platform: "android",
208
317
  safeName,
209
318
  captureId: serial,
210
- displayName: avdName || serial
319
+ displayName: avdName || serial,
320
+ screenSize,
321
+ resolution: resolution ?? { width: 0, height: 0 }
211
322
  });
212
323
  }
213
324
  return devices;
@@ -358,11 +469,11 @@ function getFramePyPath() {
358
469
  "Could not find vendored frame.py. Ensure the vendor/ directory is present."
359
470
  );
360
471
  }
361
- async function frameScreenshots(rawDir, framedDir, force = false) {
472
+ async function frameScreenshots(inputDir, outputDir, force = false) {
362
473
  await ensureVenv();
363
474
  const framePy = getFramePyPath();
364
475
  const python = getVenvPython();
365
- const args = [framePy, rawDir, framedDir];
476
+ const args = [framePy, inputDir, outputDir];
366
477
  if (force) {
367
478
  args.push("--force");
368
479
  }
@@ -370,22 +481,24 @@ async function frameScreenshots(rawDir, framedDir, force = false) {
370
481
  if (output) {
371
482
  process.stdout.write(output + "\n");
372
483
  }
373
- const framedFiles = existsSync3(framedDir) ? readdirSync(framedDir).filter((f) => f.endsWith(".png")).length : 0;
374
- const rawFiles = existsSync3(rawDir) ? readdirSync(rawDir).filter((f) => f.endsWith(".png")).length : 0;
484
+ const framedFiles = existsSync3(outputDir) ? readdirSync(outputDir).filter((f) => f.endsWith(".png")).length : 0;
485
+ const rawFiles = existsSync3(inputDir) ? readdirSync(inputDir).filter((f) => f.endsWith(".png")).length : 0;
375
486
  return { framed: framedFiles, skipped: rawFiles - framedFiles };
376
487
  }
377
488
  async function frameAllIosScreenshots(screenshotsDir, force = false) {
378
489
  const iosDir = join3(screenshotsDir, "ios");
379
490
  if (!existsSync3(iosDir)) return 0;
380
491
  let totalFramed = 0;
381
- const deviceDirs = readdirSync(iosDir, { withFileTypes: true }).filter((d) => d.isDirectory());
382
- for (const deviceDir of deviceDirs) {
383
- const rawDir = join3(iosDir, deviceDir.name, "raw");
384
- if (!existsSync3(rawDir)) continue;
385
- const pngFiles = readdirSync(rawDir).filter((f) => f.endsWith(".png"));
492
+ const sizeDirs = readdirSync(iosDir, { withFileTypes: true }).filter(
493
+ (d) => d.isDirectory()
494
+ );
495
+ for (const sizeDir of sizeDirs) {
496
+ const dirPath = join3(iosDir, sizeDir.name);
497
+ const pngFiles = readdirSync(dirPath).filter(
498
+ (f) => f.endsWith(".png") && !f.includes("_framed")
499
+ );
386
500
  if (pngFiles.length === 0) continue;
387
- const framedDir = join3(iosDir, deviceDir.name, "framed");
388
- const { framed } = await frameScreenshots(rawDir, framedDir, force);
501
+ const { framed } = await frameScreenshots(dirPath, dirPath, force);
389
502
  totalFramed += framed;
390
503
  }
391
504
  return totalFramed;
@@ -418,18 +531,17 @@ async function captureCommand(options) {
418
531
  Detected ${devices.length} device(s) with ${bundleId}:`)
419
532
  );
420
533
  for (const device of devices) {
421
- const deviceDir = join4(outputDir, device.platform, device.safeName);
422
- const rawDir = join4(deviceDir, "raw");
423
- if (existsSync4(rawDir)) {
424
- const count = readdirSync2(rawDir).filter(
425
- (f) => f.endsWith(".png")
534
+ const sizeDir = join4(outputDir, device.platform, device.screenSize);
535
+ if (existsSync4(sizeDir)) {
536
+ const count = readdirSync2(sizeDir).filter(
537
+ (f) => f.endsWith(".png") && !f.includes("_framed")
426
538
  ).length;
427
539
  console.log(
428
- ` ${pc.dim(device.platform + "/")}${device.safeName} (${device.displayName}) - ${count} screenshot(s)`
540
+ ` ${pc.dim(device.platform + "/")}${device.screenSize} ${pc.dim("(" + device.displayName + ")")} - ${count} screenshot(s)`
429
541
  );
430
542
  } else {
431
543
  console.log(
432
- ` ${pc.dim(device.platform + "/")}${device.safeName} (${device.displayName}) - ${pc.green("new")}`
544
+ ` ${pc.dim(device.platform + "/")}${device.screenSize} ${pc.dim("(" + device.displayName + ")")} - ${pc.green("new")}`
433
545
  );
434
546
  }
435
547
  }
@@ -458,9 +570,8 @@ Detected ${devices.length} device(s) with ${bundleId}:`)
458
570
  const existingFile = join4(
459
571
  outputDir,
460
572
  firstDevice.platform,
461
- firstDevice.safeName,
462
- "raw",
463
- `${screenshotName}_${firstDevice.safeName}.png`
573
+ firstDevice.screenSize,
574
+ `${screenshotName}.png`
464
575
  );
465
576
  if (existsSync4(existingFile)) {
466
577
  const response = await prompts({
@@ -492,10 +603,12 @@ Detected ${devices.length} device(s) with ${bundleId}:`)
492
603
  console.log("");
493
604
  const captured = [];
494
605
  for (const device of devices) {
495
- const filename = `${screenshotName}_${device.safeName}.png`;
496
- const tmpPath = join4(tmpDir, filename);
606
+ const filename = `${screenshotName}.png`;
607
+ const tmpPath = join4(tmpDir, `${device.platform}_${device.screenSize}_${filename}`);
497
608
  const icon = device.platform === "ios" ? "iOS" : "Android";
498
- const s = ora(`${icon}: Capturing from ${device.displayName}...`).start();
609
+ const s = ora(
610
+ `${icon}: Capturing from ${device.displayName}...`
611
+ ).start();
499
612
  let success = false;
500
613
  if (device.platform === "ios") {
501
614
  success = await captureIosScreenshot(device.captureId, tmpPath);
@@ -514,25 +627,21 @@ Detected ${devices.length} device(s) with ${bundleId}:`)
514
627
  if (success) {
515
628
  captured.push({
516
629
  platform: device.platform,
517
- safeName: device.safeName,
630
+ screenSize: device.screenSize,
518
631
  filename,
519
632
  tmpPath
520
633
  });
521
- s.succeed(`${icon}: ${device.displayName}`);
634
+ s.succeed(`${icon}: ${device.screenSize} (${device.displayName})`);
522
635
  } else {
523
636
  s.fail(`${icon}: Failed to capture from ${device.displayName}`);
524
637
  }
525
638
  }
526
639
  for (const file of captured) {
527
- const destDir = join4(outputDir, file.platform, file.safeName, "raw");
640
+ const destDir = join4(outputDir, file.platform, file.screenSize);
528
641
  mkdirSync(destDir, { recursive: true });
529
- if (file.platform === "ios") {
530
- mkdirSync(join4(outputDir, file.platform, file.safeName, "framed"), {
531
- recursive: true
532
- });
533
- }
534
642
  renameSync(file.tmpPath, join4(destDir, file.filename));
535
643
  }
644
+ updateMetadata(outputDir, devices);
536
645
  if (iosDevices.length > 0) {
537
646
  await clearIosStatusBar();
538
647
  }
@@ -561,19 +670,35 @@ Captured ${captured.length} screenshot(s) as '${screenshotName}'.`
561
670
  function getExistingScreenshotNames(outputDir, devices) {
562
671
  const names = /* @__PURE__ */ new Set();
563
672
  for (const device of devices) {
564
- const rawDir = join4(outputDir, device.platform, device.safeName, "raw");
565
- if (!existsSync4(rawDir)) continue;
566
- for (const file of readdirSync2(rawDir)) {
567
- if (!file.endsWith(".png")) continue;
568
- const base = file.replace(".png", "");
569
- const suffix = `_${device.safeName}`;
570
- if (base.endsWith(suffix)) {
571
- names.add(base.slice(0, -suffix.length));
572
- }
673
+ const sizeDir = join4(outputDir, device.platform, device.screenSize);
674
+ if (!existsSync4(sizeDir)) continue;
675
+ for (const file of readdirSync2(sizeDir)) {
676
+ if (!file.endsWith(".png") || file.includes("_framed")) continue;
677
+ names.add(file.replace(".png", ""));
573
678
  }
574
679
  }
575
680
  return [...names].sort();
576
681
  }
682
+ function updateMetadata(outputDir, devices) {
683
+ const metaPath = join4(outputDir, "metadata.json");
684
+ let metadata = { ios: {}, android: {} };
685
+ if (existsSync4(metaPath)) {
686
+ try {
687
+ metadata = JSON.parse(readFileSync(metaPath, "utf-8"));
688
+ } catch {
689
+ }
690
+ }
691
+ for (const device of devices) {
692
+ const entry = {
693
+ device: device.displayName,
694
+ id: device.captureId,
695
+ resolution: `${device.resolution.width}x${device.resolution.height}`
696
+ };
697
+ metadata[device.platform][device.screenSize] = entry;
698
+ }
699
+ mkdirSync(outputDir, { recursive: true });
700
+ writeFileSync(metaPath, JSON.stringify(metadata, null, 2) + "\n");
701
+ }
577
702
 
578
703
  // src/commands/frame.ts
579
704
  import ora2 from "ora";
@@ -605,7 +730,7 @@ async function frameCommand(dir, options) {
605
730
  var program = new Command();
606
731
  program.name("device-shots").description(
607
732
  "Capture and frame mobile app screenshots from iOS simulators and Android emulators"
608
- ).version("0.1.0");
733
+ ).version("0.2.0");
609
734
  program.command("capture").description("Capture screenshots from running devices").argument("[name]", "Screenshot name").option("-b, --bundle-id <id>", "App bundle ID").option("-o, --output <dir>", "Output directory").option("-p, --platform <platform>", "ios, android, or both").option("--no-frame", "Skip framing after capture").option("--time <time>", "Status bar time", "9:41").action(async (name, opts) => {
610
735
  await captureCommand({ name, ...opts });
611
736
  });
@@ -628,7 +753,7 @@ program.command("init").description("Create a .device-shotsrc.json config file")
628
753
  return;
629
754
  }
630
755
  const config = createDefaultConfig(response.bundleId);
631
- writeFileSync(configPath, config + "\n");
756
+ writeFileSync2(configPath, config + "\n");
632
757
  console.log(pc3.green(`Created ${configPath}`));
633
758
  });
634
759
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "device-shots",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Capture and frame mobile app screenshots from iOS simulators and Android emulators",
5
5
  "type": "module",
6
6
  "bin": {