Skip to content

Drop 6 prod deps to shrink Electron installers (~6 MB)#576

Open
IrosTheBeggar wants to merge 1 commit intomasterfrom
claude/funny-mccarthy-4e0fc8
Open

Drop 6 prod deps to shrink Electron installers (~6 MB)#576
IrosTheBeggar wants to merge 1 commit intomasterfrom
claude/funny-mccarthy-4e0fc8

Conversation

@IrosTheBeggar
Copy link
Copy Markdown
Owner

Summary

Replace 6 runtime dependencies with small, self-contained equivalents. Because package.json builds the Electron app with asar: false, every file in node_modules ships unpacked into the installer — a smaller dependency graph translates directly to faster downloads and extracts. Net savings: ~6 MB off the production node_modules tree.

What changed

Removed Replaced with Saves Sites
archiver yazl + new helper at src/util/zip-stream.js ~4.2 MB transitive (archiver-utils, tar-stream, zip-stream, compress-commons, lazystream, readdir-glob, glob, async) 4 ZIP-producing endpoints
undici node:https.request ~1.5 MB 1 (Syncthing TLS-ignore POST)
m3u8-parser inline line parser ~518 KB 1 (m3u song extraction)
mime-types static map at src/util/image-mime.js ~250 KB transitive (mime-db) — note: stays as Express transitive 3 (album art ext lookup)
nanoid crypto.randomBytes helper at src/util/ids.js ~13 KB 6 (id generation)
cookie-parser inline middleware in src/server.js ~13 KB 1 (read-only x-access-token cookie)

What was kept (and why)

  • tree-kill — handles cross-platform process-tree termination: taskkill /T /F on Windows, recursive pgrep/ps on Unix. Re-implementing this correctly is ~80 lines for 8 KB savings.
  • command-exists — handles input sanitization + file-path-vs-PATH-name detection. Savings (13 KB) too small to justify any replacement.
  • joi, winston, electron-updater — discussed; kept.

Side-effect bug fixes uncovered during the audit

  • src/util/m3u.js: the existing fallback path returned #EXTM3U as a track URI when the parser yielded zero segments. Inline parser filters #-prefixed lines correctly.
  • src/db/scanner.mjs: used to produce filenames like <hash>.false when music-metadata returned an unrecognized picture format (mime.extension(...) returns boolean false). Now falls back to .jpg like src/api/ytdl.js already did.
  • src/db/image-compress-script.js: existing mime.lookup(ext).startsWith('image') could throw Cannot read property 'startsWith' of false on unknown extensions. New helper returns a clean boolean.

Cookie-parser swap: hardening + parity tests

After the initial inline-middleware swap, the audit against cookie-parser 1.4.7's source surfaced four small differences worth closing. The middleware now uses:

  • Object.create(null) for req.cookies (no prototype-pollution surprises if a cookie name collides with Object.prototype keys like toString)
  • Idempotency guard (if (!req.cookies)) — matches cookie-parser when middleware is mounted twice
  • RFC 6265 surrounding-quote stripping ("value"value)
  • %-presence check before decodeURIComponent (cheap perf parity)

Behavior is locked in by 24 parity tests at test/cookie-middleware-parity.test.mjs covering empty headers, JWT-with-==-padding, multi-cookie headers, whitespace+tabs, leading/trailing semicolons, duplicate keys (first wins), quoted values, percent-encoding, malformed percent-encoding, no-equals, equals-in-value, and Object.prototype-collision names. Each expected value is validated against cookie-parser 1.4.7's audited output.

Verification

  • npm test: 450/450 pass (24 new cookie parity tests; baseline 426 unchanged for Subsonic / DLNA / scanner / lyrics / waveform suites)
  • npm run lint: 82 problems before / 82 after — zero new lint issues
  • Live server smoke test with the worktree's test fixture music:
    • Logged in via x-access-token header and via cookie — both work
    • POST /api/v1/download/m3u → 23 KB ZIP, 3 mp3s + the m3u file ✅
    • POST /api/v1/download/directory (flat) → 22 KB, 3 files ✅
    • POST /api/v1/download/directory (recursive, 2 subdirs) → 30 KB, 4 files preserving structure ✅
    • POST /api/v1/download/zip (file array) → 22 KB ✅
    • POST /api/v1/download/zip with UTF-8 filename café_日本語.mp3 → round-trips correctly ✅
    • GET /api/v1/admin/logs/download → valid ZIP ✅
    • All ZIPs pass zipfile.testzip() integrity check

Test plan

  • CI green
  • npm install produces a smaller node_modules (archiver, undici, m3u8-parser, nanoid, cookie-parser should be gone from prod tree; mime-types stays as Express transitive)
  • Smoke-test downloads against your real library: an album folder, an .m3u playlist, a multi-album zip, and admin/logs/download
  • Confirm Syncthing reboot still works (the undici → https.request swap is the only Syncthing-touching change)
  • Confirm share-link generation produces URL-safe IDs (the nanoidcrypto.randomBytes('base64url') swap)

🤖 Generated with Claude Code

Replace runtime dependencies whose features mStream barely exercises with
small, self-contained equivalents. With asar=false, every node_modules file
ships unpacked into the installer, so a smaller dependency graph translates
directly to faster downloads and extracts.

Removed:
- archiver         → yazl (~4.2 MB transitive: archiver-utils, tar-stream,
                    zip-stream, compress-commons, lazystream, readdir-glob,
                    glob, async). New helper at src/util/zip-stream.js
                    centralizes the create-zip-and-pipe-to-response pattern
                    used by 4 endpoints; recursive directory walks use
                    fs.readdir({ recursive: true, withFileTypes: true }).
- undici           → node:https.request (~1.5 MB). Single use was a
                    TLS-cert-ignoring POST to Syncthing's REST API.
- m3u8-parser      → simple line parser (~518 KB). Used only to extract URIs
                    from .m3u music playlists; the library is built for HLS
                    streaming manifests we never produce. Replacement also
                    fixes a latent bug where the previous fallback path
                    returned `#EXTM3U` as a "song".
- mime-types       → static image-MIME map at src/util/image-mime.js. Three
                    narrow call sites (album art extension lookup); the full
                    mime-db isn't needed.
- nanoid           → crypto.randomBytes via src/util/ids.js. Six call sites,
                    all using the default alphabet.
- cookie-parser    → inline middleware in src/server.js. Hardened with
                    Object.create(null), idempotency guard, RFC 6265 quote
                    stripping, and `%`-presence check before
                    decodeURIComponent. Behavior locked in by 24 parity
                    tests at test/cookie-middleware-parity.test.mjs
                    validated against cookie-parser 1.4.7's audited output.

Side-effect bug fixes uncovered during the audit:
- src/util/m3u.js fallback no longer returns #EXTM3U as a track URI.
- src/db/scanner.mjs no longer produces filenames like "<hash>.false" when
  music-metadata returns an unrecognized picture format (now falls back to
  ".jpg" like ytdl.js already did).
- src/db/image-compress-script.js no longer throws "Cannot read property
  'startsWith' of false" on unknown extensions.

Verification:
- npm test: 450/450 pass (24 new cookie parity tests; baseline 426 unchanged).
- npm run lint: 82 problems before / 82 after — no new issues.
- Live server smoke test: logged in via header AND cookie auth, exercised
  all four ZIP-producing endpoints (download/m3u, download/directory,
  download/zip, admin/logs/download) including a recursive directory walk
  and a UTF-8 filename round-trip; every ZIP passes integrity check.

Kept:
- tree-kill — handles cross-platform process-tree termination
  (taskkill /T /F on Windows, recursive pgrep/ps on Unix); 80 lines of
  re-implementation for 8 KB savings is bad ROI.
- command-exists — savings (13 KB) too small to justify replacement.
- joi, winston, electron-updater — kept per discussion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant