The Problem
Manually saving a website for offline use is tedious and error-prone. Standard browser "Save As" misses dynamically loaded assets, breaks relative links, and fails on sites with complex resource graphs. You need a complete, recursive mirror with all CSS, JavaScript, and images intact, bundled into a single archive.
What This Does
Website-downloader is a Node.js/Express web app that takes a URL, runs wget with mirror flags to fetch the entire site, then zips the result and streams it back to the browser over a WebSocket. The heavy lifting lives in wget/index.js (download logic) and archiver/index.js (compression). The UI is a minimal Handlebars page (views/index.hbs) with a form to submit the target URL.
The project is a fork of AhmadIbrahiim/Website-downloader (5,210 stars) with a single-star footprint here. It's a straightforward, single-purpose tool with no database, no auth, and no background job queue.
How It Is Wired
Execution starts at app.js, which sets up Express, routes, and the HTTP server. The routes/index.js handler receives the POSTed URL and kicks off the pipeline. The flow is short: routes/index.js → wget/index.js (spawns the wget process) → archiver/index.js (zips the downloaded directory) → socket/socket.js (pushes the archive to the client).
The internal call graph is small: 6 modules, 5 import edges, 0 circular dependencies. archiver/index.js is the hub with 2 importers (from routes/index.js and socket/socket.js), making it the highest-blast-radius file—any change to its API breaks both entry points. wget/index.js defines removePartiallyDownloadedFiles and getWebsiteFolderName, the only two named functions in the repo.
The external effect is entirely filesystem and network: wget writes a mirrored site to disk, archiver compresses it, and the socket sends the .zip to the browser. No database, no external API calls beyond the target site itself.
How To Use It
git clone https://github.com/moses-y/Website-downloader
cd Website-downloader
npm install
npm start
# open http://localhost:3000/
No environment variables or config files are required. The server runs on port 3000 by default. The bin/www file is the actual process entry point, launched via the start script in package.json.
Real-World Use
A support engineer needs to archive a client's legacy marketing site before decommissioning the server. They deploy this app, paste the URL, and receive a complete .zip with all HTML, CSS, JS, and images—usable for offline review or legal record-keeping. The live demo at website-downloader.onrender.com shows the intended workflow.
Code Health & Issues
Static analysis (not opinion) found the following:
- High –
evalover a runtime value inwget/index.js:exec()runs a computed string, allowing remote code execution if the URL is attacker-controlled. Fix: parse the value into the expected type. - Medium –
GITHUB_TOKENlacks least-privilege permissions in.github/workflows/codeql-analysis.yml: nopermissionsblock means the token inherits repo defaults. Fix: addpermissions: contents: read. - Medium – No Dependabot/Renovate config: 1 manifest, no update bot. Fix: add
.github/dependabot.yml. - Medium – No dependency vulnerability scan in CI. Fix: add
dependency-review-actionon PRs. - Low – No
timeout-minuteson workflow jobs incodeql-analysis.yml. Fix: add realistic bounds.
SDLC observation: no test files exist, and the wget/archiver pipeline is entirely untested. The codebase is small enough that this is a known risk, not a blocker.
The Bottom Line
A functional, single-purpose tool that does one thing well: mirror and zip a website. The eval issue in wget/index.js is a real security concern for any public deployment, and the lack of tests makes changes risky. Suitable for internal tooling or a controlled demo, not for production without addressing the code health findings first.