PhotoDrop Packaging

Can We Ship Zero-Dep to HK/TW Photographers?
R1 — 25 Feb 2026 — /deeptechresearch

I. TL;DR

True zero-dependency (download one thing, double-click, works) requires Electron or Tauri — 2–3 days of engineering + $99/year Apple Developer ID for macOS signing. That’s overkill for a free Threads giveaway.

The practical path: publish to npm, swap cloudflared for a zero-account tunnel, and ship it as a 2-command install. The photographer runs brew install node once, then npx photodrop /path every time. Everything else auto-downloads.

The content angle — “your photos never leave your machine” — is the viral hook. The 2-command install is simple enough for a Threads post. Don’t over-engineer the distribution before validating the content resonance.

Verdict: TRIAL — npm publish + localtunnel swap Ship as npx photodrop first. If Threads traction validates demand, upgrade to Electron .app. Don't build the installer before you know anyone wants it.

II. Executive Assessment

The Problem

PhotoDrop works. It serves photos from an SD card over a Cloudflare Tunnel so recipients browse and download without uploading to any cloud. But right now it requires: Node.js, npm, 4 npm packages (including native binaries), and cloudflared with a Cloudflare account. That’s fine for Eric. It’s not distributable to a photographer who just wants to share hackathon photos.

The Dependency Chain

Every packaging decision flows from what PhotoDrop actually depends on:

DependencyTypeSizePackaging Difficulty
Node.jsRuntime~40 MBEmbeddable in Electron/Tauri; or user installs via Homebrew
sharpNative C++ (libvips)~8 MBHARD — prebuilt binaries auto-download via npm, but cannot be embedded in SEA/Bun/Deno compile123
exiftool-vendoredPerl binary bundle~20 MBWorks in Electron; auto-downloads via npm4
archiverPure JS<1 MBNo issue
expressPure JS<1 MBNo issue
cloudflaredGo binary~50 MBRequires Cloudflare account for named tunnels; separate install5

sharp is the structural blocker. It’s what makes single-binary compilation impossible across every tool tested.

Readiness Assessment

DimensionRatingEvidence
Single-binary Node.js (SEA)FAILSNode.js 25.5 --build-sea cannot bundle native .node files1
Bun compileFAILSSharp regression in v1.3.5, cross-compile broken, still requires node_modules alongside binary2
Deno compileFAILSSharp “could not load module” error in compiled binaries; open issue, no fix3
pkg (Vercel)DEPRECATEDArchived Jan 2024. Final version 5.8.1.6
ElectronWORKSelectron-rebuild handles sharp; extraResources for cloudflared7
TauriCOMPLEXSidecar pattern works but requires rewriting UI in web + Rust bridge8
npm / npxWORKSsharp prebuilds auto-download per platform; exiftool-vendored self-contained9

Bottom Line

Zero-dependency = Electron-only path. Every single-binary compiler (SEA, Bun, Deno, pkg) fails on sharp’s native .node bindings. The WASM alternative (wasm-vips) is 8.3x slower and still v0.0.13.10 Electron works but costs 2–3 days + $99/year Apple Developer ID.

III. The Five Packaging Options

Option A: npm publish (npx photodrop)

User installs Node.js (one-time) ↓ npx photodrop /Volumes/SD_CARD ↓ npm auto-downloads: sharp (prebuilt) + exiftool (Perl) + express + archiver ↓ Server starts on localhost:3000 ↓ localtunnel opens public URL (no account needed) ↓ User shares URL on WhatsApp
DimensionAssessment
User effortbrew install node once, then npx photodrop /path each time
DependenciesNode.js only. Everything else auto-downloads.
Download size~70 MB (node_modules cached after first run)
PlatformsmacOS (arm64 + x64), Windows, Linux
Engineering effort~2–4 hours (swap cloudflared → localtunnel, publish to npm)
Tunnellocaltunnel (npm, zero account) or cloudflared (if installed)
Recommended for Threads launch. The HK/TW photography community skews Mac. brew install node is one command. The Threads post can include a 3-line code block that looks clean and impressive. Ship this first, validate demand, then upgrade if warranted.

Option B: Electron .app (true zero-dep)

DimensionAssessment
User effortDownload .dmg, drag to Applications, double-click
DependenciesNone
Download size~200–300 MB (Chromium + Node + sharp + exiftool + cloudflared)
PlatformsmacOS arm64, macOS x64, Windows x64 (separate builds)
Engineering effort2–3 days. Plus: Apple Developer ID ($99/year), code signing, notarization11
MaintenanceOngoing: Electron version updates, dependency rebuilds per platform
macOS Gatekeeper is a real barrier. Unsigned .app files trigger “Apple cannot check for malware” warnings. Users must right-click → Open → confirm. For non-technical photographers, this kills trust. Proper signing requires a $99/year Apple Developer Program enrollment and notarization submission.11

Option C: Tauri wrapper

DimensionAssessment
User effortDownload + install
Download size~30–80 MB (system WebView, no Chromium)
Engineering effort3–5 days. Requires sidecar pattern for sharp + exiftool + cloudflared.8 UI rewrite for Tauri IPC.
MaturitySidecar pattern documented but complex. Node.js-as-sidecar requires compiling Node server into binary first — circular problem with sharp.

Option D: Homebrew tap

DimensionAssessment
User effortbrew install ericsan/tools/photodrop
DependenciesHomebrew (macOS only)
Engineering effort~1 day. Formula + GitHub Actions for bottles.12
LimitationmacOS only. Formulas with native modules are tied to Node major version.12

Option E: Shell installer (curl | bash)

DimensionAssessment
User effortPaste one line in Terminal
What it doesChecks for Node, installs if missing, runs npm install -g photodrop
Engineering effort~2 hours
LimitationRequires terminal comfort. “Pipe to bash” pattern is controversial.

IV. Real-World Constraints

The Tunnel Problem

Cloudflared requires a Cloudflare account and configuration. For a distributable tool, this is a non-starter. Alternatives:

TunnelAccount Required?Install MethodReliabilityStatus
localtunnelNonpm (bundled)503 errors reported; unmaintained13RISKY
boreNobrew install boreStable, 10.8K GitHub stars14GOOD
cloudflared (quick)No*brew install cloudflaredEnterprise-grade5BEST
serveoNoSSH (built-in)IntermittentFRAGILE

* cloudflared tunnel --url http://localhost:3000 creates a free ephemeral tunnel with a random trycloudflare.com URL. No account, no login, no config. Named tunnels (custom domains) require an account, but the ephemeral mode is perfect for distribution.5

cloudflared ephemeral mode is the answer. Zero config, zero account. User gets a random https://xxx.trycloudflare.com URL. Enterprise-grade infrastructure. Already built into PhotoDrop’s startTunnel() function — it falls back to ephemeral mode when no cloudflared.yml exists.

The sharp Problem (Honest Assessment)

Sharp is PhotoDrop’s heaviest dependency. It compiles libvips (a C library) into platform-specific .node binaries. This is what kills every single-binary packager. But there’s nuance:

For the npm distribution path, sharp is a non-issue — it just works. It only becomes a problem if we pursue single-binary or Electron.

The exiftool Problem

exiftool-vendored bundles a Perl runtime + ExifTool script (~20 MB, 292 files).4 It auto-downloads via npm and works cross-platform. For Electron, it’s documented to work with electron-builder.16 Not a blocking issue for any path.

Do photographers have Node.js?

Honest answer: most don’t. But:

V. Critical Assessment

Challenge 1: “Zero-dep is the wrong goal for a Threads post.”

The Threads post is content marketing, not product distribution. The value is the story (“your photos never leave your machine”), not the install experience. A 2-command install is simple enough to screenshot in a code block. If 500 people see the post, maybe 20 try it, and those 20 are technical enough to run terminal commands. Optimizing for the other 480 (who will never install anything) is wasted effort.

For npm-first approach

  • 2–4 hours to ship vs 2–3 days for Electron
  • Cross-platform for free (sharp prebuilds cover Mac/Win/Linux)
  • Auto-updates: user always gets latest via npx
  • The Threads audience self-selects for capability
  • Photography community is Mac-heavy (Homebrew is standard)
  • Story is the hook, not the installer

Against npm-first approach

  • Node.js IS a dependency (40 MB)
  • Terminal commands intimidate some photographers
  • “Just works” is always better UX than “run this command”
  • Windows users need to install Node differently
  • First-run npm install takes 30–60 seconds (feels broken to novices)

Challenge 2: “Localtunnel is unreliable.”

True. 503 errors documented, project barely maintained.13 But cloudflared’s ephemeral mode (cloudflared tunnel --url) is enterprise-grade, requires zero account, and is already in PhotoDrop. The user just needs brew install cloudflared. Two installs total: brew install node cloudflared. Still one line.

Challenge 3: “Nobody will use a tool that requires Terminal.”

Counter: Homebrew has 620K+ GitHub stars. npx has millions of daily executions. The Mac photography community already uses Terminal for ffmpeg, ImageMagick, tethering tools. The audience Eric is targeting (HK/TW photographers who shoot RAW) are not grandmothers — they’re people who chose to shoot RAW over JPEG, which is itself a technical decision.

Challenge 4: “Existing tools already solve this.”

Checked. gallery-server (npm) does photo serving but no RAW support, no tunneling, no embedded preview extraction.17 ezshare does LAN file sharing but no gallery UI, no internet access.18 Nothing combines RAW preview extraction + gallery UI + tunnel in one command. PhotoDrop is genuinely novel.

VI. Recommended Approach

Phase 1: npm publish (ship this week)

Changes needed to make PhotoDrop distributable via npx:

ChangeEffortWhy
Keep cloudflared ephemeral mode as default0 (already built)Zero-account tunnel, enterprise-grade
Add fallback if cloudflared not installed30 minPrint install instructions instead of crashing
Clean up CLI arguments30 minNice --help, version flag, clear error messages
Add #!/usr/bin/env node shebangAlready thereMakes npx work
Publish to npm as photodrop15 minnpm publish
Add README with install instructions30 minnpm page + GitHub

Result: Any Mac user runs:

brew install node cloudflared        # one-time setup
npx photodrop /Volumes/UNTITLED      # each time

That’s it. Server starts, tunnel opens, URL prints. Copy URL, send on WhatsApp.

Phase 2: Validate via Threads (same week)

Post to HK + TW Threads. Hook: “built a tool for photographers who are tired of uploading RAW files to random cloud services. Your photos stay on YOUR machine. Tunnel creates a link. Recipient browses + downloads.” Include screenshot of gallery UI + the 2-line install.

Phase 3: Upgrade if validated

SignalAction
Threads post gets >50 saves/bookmarksBuild Homebrew tap (brew install photodrop = single command)
Threads post gets >200 saves + DM requestsBuild Electron .app for true zero-dep. Invest $99 in Apple Developer ID.
Threads post gets <20 engagementShip to Eric’s personal workflow. Don’t invest further packaging.

VII. Implementation Checklist

To publish as npx photodrop

  1. Verify shebang line: #!/usr/bin/env node at top of server.js
  2. Set "bin": { "photodrop": "./server.js" } in package.json (already done)
  3. Add "keywords", "repository", "license" to package.json
  4. Graceful cloudflared detection: if not installed, print URL for manual download + still run localhost
  5. npm login + npm publish
  6. Test: npx photodrop ~/Desktop/test-photos from a clean machine

Gotchas

VIII. Verdict & Recommendations

TRIAL — npm publish first, Electron only if validated.

The honest answer to “can we ship zero-dep?” is: yes, but it costs 2–3 days + $99/year and the engineering is not trivial (Electron + code signing + notarization + platform-specific builds). That’s disproportionate to the goal of a free Threads giveaway.

The npm path gets 90% of the way there with 10% of the effort. The remaining 10% (installing Node.js) is one Homebrew command. For a HK/TW photographer audience reached via Threads, this is sufficient.

What would change the verdict:

References

[1] Progosling — Node.js 25.5 SEA — native modules require explicit platform-specific bundling (Jan 2026)
[2] Bun #25635 — Sharp fails on linux/arm64 in compiled binary, subpath exports regression (2025)
[3] Sharp #3912 — Deno compile: “could not load the sharp module” in standalone binary (2024)
[4] npmjs.com/exiftool-vendored.pl — 20.3 MB, 292 files, Perl binary bundle (2025)
[5] Cloudflare Tunnel docs — ephemeral mode: cloudflared tunnel --url, no account required
[6] vercel/pkg — archived Jan 2024, v5.8.1 final. Recommends Node.js SEA instead.
[7] Electron docs — native module compilation via @electron/rebuild
[8] Tauri sidecar docs — embedding external binaries with platform-specific suffixes
[9] Sharp install docs — prebuilt binaries auto-download for macOS (arm64, x64), Windows, Linux
[10] libvips WASM benchmark — wasm-vips 5.36 ops/sec vs sharp 44.35 ops/sec (JPEG resize)
[11] Apple Developer docs — notarization required for Gatekeeper pass; Developer ID $99/year
[12] Homebrew docs — Node.js formula authoring; native addons tied to Node major version
[13] LocalXpose blog — localtunnel lacks active maintenance, 503 errors documented (2026)
[14] bore-cli on crates.io — 10.8K stars, v0.6.0, MIT license (Jun 2025)
[15] Photon — Rust→WASM image library, 90+ functions, 4–10x faster than pure JS
[16] exiftool-vendored docs — Electron-builder support, 500+ downstream projects
[17] gallery-server — simple photo gallery, no RAW support, no tunneling
[18] ezshare — LAN file sharing, no gallery UI, no internet access