npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

app-deploy-kit

v0.1.1

Published

CLI toolkit for automating app store deployment — screenshots, listings, graphics, and upload

Readme

deploy-kit

npm version license node

CLI toolkit for automating app-store deployment to Google Play and the Apple App Store. Captures screenshots from a running web app, frames them with device bezels, generates store listings, and creates feature graphics — all from the command line.

Published on npm as app-deploy-kit; the CLI command is deploy-kit.

Scope: capture uses headless Chromium (Puppeteer), so it works for any web-renderable UI — plain web apps, Expo web, Flutter web, Capacitor, static sites. For native-only iOS/Android builds you'll need a simulator-based tool (e.g. fastlane snapshot, Maestro) to produce raw PNGs; the rest of deploy-kit (frame, listing, graphics) still works if you drop those PNGs in deploy-kit/screenshots/raw/.

Install

npm install -g app-deploy-kit

Requires:

  • Node 20+
  • Puppeteer (bundled). On Linux, deploy-kit auto-detects a system Chromium at /usr/bin/chromium, /usr/bin/chromium-browser, or /usr/bin/google-chrome(-stable) — useful on ARM (Raspberry Pi, ARM cloud) where Puppeteer's bundled Chromium doesn't run. Override with PUPPETEER_EXECUTABLE_PATH=/path/to/chrome if needed.
  • An Anthropic API key — only needed for listing generate.

Quick start

# 1. Initialize config in your app project
deploy-kit init --name "My App" --bundle com.example.myapp --tagline "Fast, focused, fair." --color "#6B5BFF" --locales en-US,fr-FR

# 2. Start your dev server, then edit deploy-kit/config.json:
#    - set screenshots.capture.baseUrl to your running app
#    - list the routes you want in screenshots.capture.routes
#    - (optional) add prerequisites for backend services that must be up
# Then capture raw screenshots:
deploy-kit capture

# 3. Generate store listing copy (requires ANTHROPIC_API_KEY)
deploy-kit listing generate --from README.md --locale all

# 4. Frame screenshots with device bezels
deploy-kit frame

# 5. Generate feature graphic and social card
deploy-kit graphics generate

Commands

deploy-kit init

Initialize config for an app. Creates deploy-kit/ directory with config and folder structure.

deploy-kit init --name "My App" --bundle com.example.app \
  [--tagline "Your tagline"] [--color "#007AFF"] [--locales en-US,fr-FR]

Generated config includes 3 device defaults (iphone-15-pro, pixel-8, ipad-pro-11) with bezel references and a framing.caption block you can edit.

deploy-kit listing generate

Generate ASO-optimized store listing text using Claude. Outputs metadata in fastlane-compatible format for both iOS (metadata/) and Google Play (metadata-google/).

deploy-kit listing generate --from README.md \
  [--keywords "todo,productivity,minimal"] [--locale en-US|all]

Pass --locale all to generate listings for every locale in your config.

deploy-kit capture

Capture raw screenshots from a running web app and write them to deploy-kit/screenshots/raw/ — exactly where frame expects them.

deploy-kit capture [--base-url http://localhost:3000] \
  [--viewport phone] [--route home] [--output deploy-kit/screenshots/raw]

Configure under screenshots.capture in deploy-kit/config.json:

{
  "screenshots": {
    "capture": {
      "baseUrl": "http://localhost:3000",
      "viewports": [
        { "name": "phone",  "width": 390, "height": 844,  "deviceScaleFactor": 3 },
        { "name": "tablet", "width": 834, "height": 1194, "deviceScaleFactor": 2 }
      ],
      "routes": [
        { "name": "home", "path": "/" },
        { "name": "editor", "path": "/editor", "waitFor": "#canvas-ready" },
        { "name": "settings", "path": "/settings", "hook": "deploy-kit/hooks/settings.mjs" }
      ],
      "prerequisites": [
        { "name": "API server", "url": "http://localhost:3001/health" }
      ],
      "waitForSelector": "body",
      "navigationTimeoutMs": 30000,
      "selectorTimeoutMs": 10000
    }
  }
}
  • viewports[] — capture viewport in CSS pixels. Output PNG resolution = width × deviceScaleFactor × height × deviceScaleFactor.
  • routes[].waitFor — CSS selector to wait for before snapping (per-route override of waitForSelector).
  • prerequisites[] — services that must be reachable before capture starts. Each is fetched once; any HTTP response counts as up. If any fail, capture exits with a clear error before launching the browser. Use this for backend APIs the SPA proxies to.
  • navigationTimeoutMs — max time for page.goto (default 30000). Bump for slow first-route compiles.
  • selectorTimeoutMs — max time waiting for waitForSelector (default 10000).
  • routes[].hook — path to a JS file that runs after navigation and before screenshot. Use it to dismiss onboarding splashes, set auth, scroll, fill mock data, or anything else that puts the page in a screenshot-ready state.

A common SPA pattern: dismiss a first-launch onboarding overlay that's gated on a localStorage flag.

// deploy-kit/hooks/dismiss-onboarding.mjs
export default async function dismissOnboarding(page, { route }) {
  await page.evaluate(() => {
    localStorage.setItem("onboarding-seen", "1");
  });
  await page.reload({ waitUntil: "domcontentloaded" });
  await page.waitForSelector(`app-${route.name}`, { timeout: 30000 });
}

Or a login flow that gates protected routes:

// deploy-kit/hooks/login.mjs
export default async function login(page) {
  await page.click('[data-test="login"]');
  await page.type("#email", "[email protected]");
  await page.type("#password", "hunter2");
  await page.click('button[type="submit"]');
  await page.waitForSelector('[data-test="dashboard-loaded"]');
}

Output filenames are <viewport>--<route>.png (e.g. phone--home.png). CLI flags (--base-url, --viewport, --route) override config values for ad-hoc runs.

Viewport ↔ device matching: each device entry can specify a "viewport": "phone" | "tablet" | "<custom>". frame only pairs a raw screenshot with a device when the screenshot's filename prefix (<viewport>--<route>.png, written by capture) matches the device's viewport field. Devices without a viewport field accept all screenshots (back-compat). Default devices ship with sensible mappings: phone-shape devices (iPhone, Pixel) get phone-viewport captures; iPad gets tablet-viewport captures.

deploy-kit frame

Frame raw screenshots with device bezels and marketing captions.

deploy-kit frame [--device iphone-15-pro] [--locale en-US]

By default, frames every device in your config and writes one PNG per (raw file × device) pair into deploy-kit/screenshots/framed/<device>/<basename>.png. Use --device to target a single device.

deploy-kit graphics generate

Generate a Google Play feature graphic (1024×500) and social OG card (1200×630).

deploy-kit graphics generate [--output deploy-kit/graphics]

Framing screenshots

deploy-kit frame takes raw app screenshots and outputs upload-ready framed PNGs — device bezel scaled to fit, caption, background gradient. Output dimensions match the device's native screen resolution (e.g. 1320×2868 for iPhone 16 Pro Max) so you can upload directly to App Store Connect and Google Play with no manual resize step.

Bundled device templates: iPhone 16 Pro Max (6.9", 1320×2868 — the only iPhone size App Store Connect requires; auto-scales to smaller iPhones), iPhone 15 Pro (6.1", 1179×2556), Pixel 8 (1080×2400), iPad Pro 11" (1668×2388). Framework-agnostic — drop in raw PNGs from any Expo / Capacitor / Flutter / native build.

Caption configuration

Edit framing.caption in deploy-kit/config.json:

{
  "framing": {
    "caption": {
      "text": "Fast, focused, fair.",
      "font": "DejaVu Sans",
      "color": "#ffffff",
      "position": "top",       // "top" | "bottom"
      "sizeRatio": 0.045        // fraction of canvas width
    }
  }
}

If caption.text is empty, frame falls back to deploy-kit/metadata/<locale>/subtitle.txt if it exists (written by listing generate).

Background

By default, the area outside the device bezel is a 135° linear gradient auto-derived from primaryColor (darkened by ~20%). Override with any CSS background value via framing.background:

{
  "framing": {
    "background": "linear-gradient(135deg, #6B5BFF, #FF5BB5)",
    // or radial: "radial-gradient(circle at top, #1a1a2e, #0f0f1a)"
    // or solid:  "#0f0f1a"
    // or image:  "url(data:image/png;base64,...) center/cover"
    "caption": { "text": "Fast, focused, fair." }
  }
}

If unset, the default auto-gradient is used. Existing configs need no change.

Landscape devices

For landscape-locked apps (games, media players), set rotate: 90 on the device and use landscape canvas dimensions. The portrait bezel rotates 90° clockwise at render time and the screenshot fills the rotated screen rect — no separate landscape bezel assets required. Works with any bundled bezel (Android or iOS).

{
  "screenshots": {
    "devices": [
      {
        "name": "pixel-8-landscape",
        "width": 2400, "height": 1080,
        "bezel": "pixel-8",
        "rotate": 90,
        "viewport": "landscape"
      },
      {
        "name": "iphone-16-pro-max-landscape",
        "width": 2868, "height": 1320,
        "bezel": "iphone-16-pro-max",
        "rotate": 90,
        "viewport": "landscape"
      },
      {
        "name": "ipad-pro-11-landscape",
        "width": 2388, "height": 1668,
        "bezel": "ipad-pro-11",
        "rotate": 90,
        "viewport": "landscape"
      }
    ],
    "capture": {
      "viewports": [
        { "name": "landscape", "width": 844, "height": 390, "deviceScaleFactor": 3 }
      ]
    }
  }
}

The capture viewport just needs to be landscape (width > height) for @media (orientation: landscape) queries to fire. Output PNG dimensions match device.width × device.height and align with both stores' specs:

  • Play Console — landscape phone slot accepts any 320–3840px PNG within 16:9 to 9:16 aspect, so a landscape Pixel 8 (2400×1080) drops straight in.
  • App Store Connect — landscape iPhone 6.9" requires exactly 2868×1320; landscape iPad Pro 11" requires exactly 2388×1668. The bundled bezels match these specs at width × height swapped.

Custom device bezels

Each device with a bezel field references a template directory under src/templates/screenshots/<bezel>/:

  • bezel.svg — chrome overlay (hollow — screen area transparent)
  • meta.json{ device: {width,height}, screen: {x,y,width,height,radius} }

Add your own by dropping a new pair under src/templates/screenshots/<name>/ and setting "bezel": "<name>" on a device entry in config.json.

Custom templates (bezel fallback)

For devices without a bezel asset, frame falls back to src/templates/frame-default.html. You can override the fallback by copying it to deploy-kit/templates/frame-default.html and editing — user templates take priority over built-in ones. Same goes for graphic-feature.html used by graphics generate.

Output structure

deploy-kit/
├── config.json
├── screenshots/
│   ├── raw/                           # Your raw screenshots
│   └── framed/                        # Output, organized by device
│       ├── iphone-15-pro/
│       ├── pixel-8/
│       └── ipad-pro-11/
├── graphics/
│   ├── feature-graphic.png
│   └── social-card.png
├── metadata/                          # iOS (fastlane-compatible)
│   └── en-US/
│       ├── name.txt
│       ├── subtitle.txt
│       ├── description.txt
│       ├── keywords.txt
│       ├── promotional_text.txt
│       └── release_notes.txt
└── metadata-google/                   # Google Play
    └── en-US/
        ├── title.txt
        ├── short_description.txt
        └── full_description.txt

Uploading to stores

deploy-kit generates assets — use existing tools to upload them.

iOS (App Store Connect)

Install asc CLI (brew install asc) or use fastlane deliver:

# fastlane
fastlane deliver --metadata_path deploy-kit/metadata/en-US \
  --screenshots_path deploy-kit/screenshots/framed

# asc CLI
asc apps screenshots upload --app-id <YOUR_APP_ID> --locale en-US \
  --display-type APP_IPHONE_67 deploy-kit/screenshots/framed/iphone-15-pro/*.png

Android (Google Play)

Use fastlane supply:

fastlane supply --metadata_path deploy-kit/metadata-google/en-US \
  --images_path deploy-kit/graphics

Development

npm install
npm run typecheck      # tsc strict + tests
npm run lint           # eslint
npm run test           # vitest + pixelmatch golden-file framing tests (tolerance 0.5%)
npm run build          # compiles to dist/, copies templates to dist/templates/

npm test compares framed output against committed PNG goldens in src/fixtures/frame/golden/. If a change intentionally alters rendering, regenerate goldens with:

UPDATE_GOLDENS=1 npm test

Then inspect and commit the new goldens.

License

MIT.

arthas.dev