RepoPilot

vercel/pkg

Package your Node.js project into an executable

Healthy

Healthy across all four use cases

HealthyDependency

Permissive license, no critical CVEs, actively maintained — safe to depend on.

HealthyFork & modify

Has a license, tests, and CI — clean foundation to fork and modify.

HealthyLearn from

Documented and popular — useful reference codebase to read through.

HealthyDeploy as-is

No critical CVEs, sane security posture — runnable as-is.

  • Stale — last commit 2y ago
  • 26+ active contributors
  • Distributed ownership (top contributor 31% of recent commits)
  • MIT licensed
  • CI configured
  • Tests present

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/vercel/pkg)](https://repopilot.app/r/vercel/pkg)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card

This card auto-renders when someone shares https://repopilot.app/r/vercel/pkg on X, Slack, or LinkedIn.

Ask AI about vercel/pkg

Grounded in the actual source code. Pick a starter question or write your own.

Or write your own question →

Onboarding doc

Onboarding: vercel/pkg

Generated by RepoPilot · 2026-06-19 · Source

🎯Verdict

GO — Healthy across all four use cases

  • 26+ active contributors
  • Distributed ownership (top contributor 31% of recent commits)
  • MIT licensed
  • CI configured
  • Tests present
  • ⚠ Stale — last commit 2y ago

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

TL;DR

pkg is a CLI tool that packages Node.js applications into standalone executables for Linux, macOS, and Windows without requiring Node.js to be installed on the target machine. It bundles your source code, dependencies, and assets into a single binary by embedding a Node.js runtime and using bytecode compilation to obfuscate your code. This solves the distribution problem for Node.js apps, enabling cross-platform deployment as self-contained binaries. Single-repo structure: lib/ contains the core packaging engine, dictionary/ holds hardcoded package-specific build configurations and asset mappings, bin/ has the CLI entry point. The tool uses a compilation pipeline that parses package.json, traces dependencies, generates bytecode via V8 snapshots, and links against prebuilt Node.js binaries stored externally.

👥Who it's for

Backend developers and Node.js application authors who need to distribute closed-source commercial applications, demos, or self-contained tools without exposing source code or requiring end-users to install Node.js and npm dependencies.

🌱Maturity & risk

pkg is formally deprecated as of version 5.8.1 (the last release), with the repository now archived. While it was a mature, production-ready tool with significant adoption, Vercel is no longer maintaining it in favor of Node.js 21's built-in single executable applications (SEA) feature. Several community forks continue to add features.

Critical risk: pkg is no longer maintained by Vercel, creating a long-term sustainability concern for new projects. The codebase relies on prebuilt Node.js binaries and native addon compilation, which may break with newer Node.js versions. Cross-compilation complexity and platform-specific build issues (especially native modules) are historically difficult to debug. The dictionary/ folder (50+ package override files) requires manual maintenance as npm modules evolve.

Active areas of work

The project is in maintenance mode only—no active development. The README displays a deprecation notice recommending users migrate to Node.js 21's native single executable applications feature. Community forks (like ncc-based alternatives) have taken over feature development.

🚀Get running

git clone https://github.com/vercel/pkg.git
cd pkg
yarn install
yarn run build
node bin/pkg.js --help

Daily commands:

# Run the packager on an entry point
node bin/pkg.js index.js

# Or for a project with package.json
node bin/pkg.js .

# Specify targets and output
node bin/pkg.js -t node16-linux,node18-win-x64 -o myapp index.js

# With bytecode disabled for debugging
node bin/pkg.js --no-bytecode index.js

🗺️Map of the codebase

  • README.md — Primary documentation explaining pkg's purpose, deprecation status, and use cases—essential context for understanding the project's scope and current maintenance status
  • .github/workflows/ci.yml — CI/CD pipeline definition that shows how the project is tested and built across platforms—critical for understanding release and validation processes
  • LICENSE — Legal foundation defining project usage rights and liability—necessary for contributors and users to understand licensing obligations
  • .eslintrc — Code quality and style standards enforced across the codebase—defines the development conventions all contributors must follow
  • .yarnrc — Package manager configuration specifying dependency resolution strategy—affects build reproducibility and dependency handling
  • dictionary/ — Registry of ~120 npm package special-case handlers for bundling—the domain knowledge layer that makes pkg understand complex module dependencies

🧩Components & responsibilities

  • pkg CLI (Node.js, Commander.js (likely)) — Entry point that parses package.json, orchestrates bundling pipeline, invokes compiler, outputs executable
    • Failure mode: Bundling fails if package.json is malformed, unsupported package version, or cross-compilation target unavailable
  • Module Dictionary (JavaScript module definitions, package-specific bundling rules) — Registry of ~120 handlers for packages with native bindings (bcrypt, grpc, better-sqlite3, etc) or dynamic requires (express, ejs, etc)
    • Failure mode: Bundled executable crashes at runtime if dictionary entry is missing or outdated for a package's new version
  • Bundler Engine (Node.js require() graph traversal, AST analysis) — Analyzes dependency tree, tree-shakes unused code, applies module handlers, collects assets matching pkg.assets patterns
    • Failure mode: Fails on circular dependencies, missing handler for native module, or asset glob patterns that match no files
  • V8 Compiler & Snapshot (V8, platform-specific compilation toolchains) — Snapshots JavaScript bytecode and compiles to native executable for target architecture (Windows x64, macOS arm64, Linux x64, etc)
    • Failure mode: Cross-compilation fails if target architecture compiler unavailable; executable crashes if snapshot incompatible with runtime
  • CI/CD Pipeline (GitHub Actions, ESLint, Yarn) — Automated testing and validation across Windows, macOS, Linux; runs linter, tests, and builds release artifacts
    • Failure mode: New linting rules or failing tests block release; stale issues not auto-closed if close-stale workflow fails

🔀Data flow

  • Developer's package.jsonpkg CLI — Configuration specifying entry point, assets glob patterns, and target Node.js versions/platforms
  • pkg CLIModule Dictionary (dictionary/) — Queries handlers for each dependency to determine bundling strategy and special-case logic
  • Module DictionaryBundler Engine — Returns

🛠️How to make changes

Add support for a new npm package requiring special bundling

  1. Analyze the target package's native bindings or dynamic require patterns (dictionary/)
  2. Create a new handler file following naming convention: dictionary/{package-name}.js (dictionary/express.js)
  3. Define the module resolution and bundling logic in the handler (reference existing handlers for patterns) (dictionary/bindings.js)
  4. Test the handler by adding the package to a test project's dependencies and running the pkg CLI (.github/workflows/ci.yml)

Configure a Node.js project for bundling with pkg

  1. Add a 'pkg' configuration section to package.json specifying assets and targets (README.md)
  2. Define asset patterns (e.g., 'views/**/*') to include non-code files in the executable (README.md)
  3. Specify target Node.js versions and platforms (e.g., 'node8', 'node12-win-x64') (README.md)
  4. Run pkg CLI against the configured package.json to generate cross-platform executables (.github/workflows/ci.yml)

Update linting standards for code quality

  1. Modify ESLint rules in .eslintrc to enforce new patterns or disable problematic rules (.eslintrc)
  2. Add exception paths to .eslintignore if certain directories need different standards (.eslintignore)
  3. Apply dictionary-specific rules via dictionary/.eslintrc.json for module handlers (dictionary/.eslintrc.json)
  4. Run linter in CI pipeline to validate compliance (.github/workflows/ci.yml)

🔧Why these technologies

  • Node.js + V8 — Core runtime being packaged; V8 snapshot and compilation capabilities enable single-executable output
  • Yarn — Deterministic dependency resolution with lock files ensures reproducible builds across platforms
  • ESLint + Prettier — Enforces consistent code quality and style across 600 files with many contributors
  • GitHub Actions CI/CD — Automates cross-platform testing (Windows, macOS, Linux) and validation before release

⚖️Trade-offs already made

  • Centralized module dictionary for special cases

    • Why: ~120 npm packages have dynamic requires, native bindings, or platform-specific logic incompatible with standard bundling
    • Consequence: Requires manual handler creation for each unsupported package; increases maintenance burden but enables broader ecosystem support
  • Single executable output with embedded assets

    • Why: Maximum portability—no npm install or Node.js runtime required on deployment machine
    • Consequence: Larger file sizes, slower startup (snapshot decompression), and difficulty debugging embedded assets compared to traditional Node.js apps
  • Project deprecated as of v5.8.1 in favor of Node.js native single-executable apps

    • Why: Node.js 21+ offers native support for single executables; reduces need for third-party tool
    • Consequence: No new feature development; users should migrate to Node.js native solution or use community forks for new work

🚫Non-goals (don't propose these)

  • Does not provide IDE debugging support for packaged executables
  • Does not handle dynamic require() calls that cannot be statically analyzed
  • Does not provide true code obfuscation or anti-tampering mechanisms
  • Does not support runtime dependency injection or plugin architectures
  • Does not optimize for smallest possible binary size
  • Does not provide Windows Installer (.msi) or macOS .dmg generation

🪤Traps & gotchas

  1. Dictionary overrides are brittle: dictionary/*.js files are hardcoded for specific npm packages and versions; new package versions may break bundling silently. 2. Native module compilation: --no-native-build requires pre-compiled native addons; building native modules from source (especially bcrypt, sqlite) is error-prone across platforms. 3. Prebuilt binaries sourced externally: pkg downloads Node.js binaries from cache; if URLs break or versions become unavailable, packaging fails with cryptic network errors. 4. Bytecode snapshot limitations: bytecode cannot capture dynamic requires, template strings with require(), or code generated at runtime—fallback to --no-bytecode increases output size. 5. macOS codesigning: --no-signature disables it, but distribution may fail if binaries aren't signed with valid developer certificate. 6. Asset glob patterns: assets/ paths in package.json use glob syntax; typos are silently ignored rather than errored.

🏗️Architecture

💡Concepts to learn

  • Bytecode snapshot (V8 SnapshotBlob) — pkg uses V8 snapshots to pre-compile JavaScript to bytecode before embedding in the binary, reducing startup time and obfuscating source; understanding snapshot limitations (dynamic requires) is critical for troubleshooting bundling failures
  • Static dependency analysis / require() tracing — pkg cannot execute code to discover dependencies, so it relies on regex/AST parsing to find require() calls; understanding its limitations (dynamic requires, require(variable)) is essential for bundling complex apps
  • Cross-compilation — pkg allows building executables for Windows, Linux, macOS from any platform by using prebuilt Node.js binaries; this is non-trivial for native addons and platform-specific code paths
  • Native addon binding resolution — Modules like bcrypt and better-sqlite3 require native .node files compiled for specific platforms and architectures; pkg's dictionary/ overrides handle this by pre-specifying which binaries to include
  • Asset embedding and file-based require() — pkg's --assets flag bundles non-code files (views, templates, configs) into the executable; understanding how paths are resolved at runtime is critical for avoiding 'file not found' errors in packaged binaries
  • Code obfuscation vs. security — Bytecode compilation obscures but does not encrypt source; pkg is marketed for 'closed-source' distribution but bytecode can still be decompiled; users must understand the threat model
  • macOS code signing and notarization — pkg integrates codesigning (--no-signature flag to skip); understanding macOS Gatekeeper and notarization is essential for distributing packaged apps on modern macOS versions
  • vercel/ncc — Vercel's bundle-to-single-file tool for JavaScript; often paired with pkg or used as alternative for Node.js CLI bundling
  • nexe/nexe — Direct competitor to pkg with similar goal (Node.js to executable) but different implementation; maintained fork ecosystem after pkg deprecation
  • nodejs/node — Node.js repository itself; understanding Node.js build system and single executable applications (SEA) feature is critical context for why pkg is now deprecated
  • oclif/oclif — Framework for building Node.js CLIs; many users of pkg are oclif developers needing to distribute CLI binaries
  • yao-pkg/pkg — Active community fork of pkg that continues feature development and bug fixes post-deprecation

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add comprehensive test coverage for dictionary/ entrypoint detection

The dictionary/ folder contains 100+ module override files (angular-bridge.js, express.js, bcrypt.js, etc.) that handle dynamic require() resolution for bundled packages. There's no visible test suite validating that these dictionary entries correctly intercept and resolve modules at runtime. This is critical since pkg's core value proposition depends on accurate module detection. New contributors could add unit tests that verify dictionary entries work correctly when packages are bundled.

  • [ ] Create test/dictionary/ directory structure
  • [ ] Add test file test/dictionary/dictionary.test.js to verify dictionary entries are properly exported
  • [ ] Add integration tests in test/dictionary/integration.test.js that bundle sample apps using dictionary-heavy packages (express, bcrypt, better-sqlite3) and verify they resolve correctly
  • [ ] Document how to add new dictionary entries in CONTRIBUTING.md with test requirements

Add CI workflow for cross-platform binary testing (.github/workflows/test-binaries.yml)

While .github/workflows/ci.yml exists for linting, there's no dedicated workflow testing pkg's core functionality: actually packaging and executing binaries on multiple OS targets. Given that pkg's entire purpose is cross-compilation (Linux, macOS, Windows), the absence of a workflow that validates packaged binaries actually run on target platforms is a significant gap.

  • [ ] Create .github/workflows/test-binaries.yml with matrix for os: [ubuntu-latest, macos-latest, windows-latest]
  • [ ] Add job to package test/fixtures/sample-app with various node targets (node14, node16, node18)
  • [ ] Add verification step that executes generated binaries and validates output
  • [ ] Document binary testing expectations in CONTRIBUTING.md

Create missing documentation for dictionary module override system (docs/dictionary-guide.md)

The dictionary/ folder is the most complex part of pkg's architecture—it solves the hard problem of making native modules and dynamic requires work in bundled executables. However, there's no documentation explaining how the dictionary system works, when new entries are needed, or how to debug dictionary issues. This creates a barrier for contributors wanting to add support for new packages.

  • [ ] Create docs/dictionary-guide.md explaining the purpose and mechanics of dictionary entries
  • [ ] Document the pattern used in dictionary entries (e.g., how express.js exports relative requires and what makes it work)
  • [ ] Add troubleshooting section with examples of common issues (missing dynamic requires, native binding paths, etc.)
  • [ ] Include step-by-step guide for adding dictionary support for a new package with reference to existing entries like dictionary/bcrypt.js and dictionary/better-sqlite3.js

🌿Good first issues

  • Add dictionary entry for a missing npm package: identify a popular package (e.g., 'sharp', 'kafka-js') with dynamic requires or native bindings that pkg doesn't yet handle, and create dictionary/package-name.js following the existing pattern in dictionary/bcrypt.js or dictionary/better-sqlite3.js.
  • Improve error messages for dependency resolution failures: many users report cryptic 'module not found' errors; audit lib/utils.js's require-tracing logic and add specific error handlers for common cases like conditional requires or optional peer dependencies, then document in README.
  • Write integration tests for the CLI: the .github/workflows/ci.yml pipeline exists but test coverage for specific --targets and compression algorithms is sparse; add test fixtures in a new tests/ folder covering node16-linux + node18-win combinations and --compress brotli scenarios.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 9066cee — Deprecate pkg. (leerob)
  • bb04269 — build(deps): bump semver from 6.3.0 to 6.3.1 (#1958) (dependabot[bot])
  • ace66be — build(deps): bump word-wrap from 1.2.3 to 1.2.4 (#1965) (dependabot[bot])
  • 73a03d1 — fix: missing entrypoint when launched from self-created child process (#1949) (Luzzifus)
  • e51efbe — fix: Add missing functions from restored fs.Stats (#1923) (phated)
  • 76010f6 — chore: bump pkg-fetch to 3.5.2 (#1914) (dav-is)
  • 7255f64 — test: ignore pnpm test for node14 (#1919) (emmansun)
  • c353cc9 — chore: remove unused dependencies (#1769) (ignatiusmb)
  • 265c00e — test: update tesseract.js test for v4 (#1864) (some1chan)
  • 4fe0b4d — chore: remove eol nodejs in tests (#1889) (Corentin Mors)

🔒Security observations

This codebase has significant security concerns. The primary risk is that pkg itself is deprecated and no longer maintained, meaning any discovered vulnerabilities will not be patched. Additionally, the Express dependency (4.15.2) is severely outdated with multiple known vulnerabilities, and the Node.js target version (8) is also end-of-life. The asset bundling pattern is overly permissive and could inadvertently include sensitive files. Immediate action is required to either migrate away from pkg to a maintained alternative or update all dependencies to currently supported versions. Without these changes, any application built with this configuration will inherit multiple known security vulnerabilities.

  • High · Outdated Express Dependency — package.json - dependencies.express. The package.json specifies Express 4.15.2, which was released in April 2017 and contains multiple known security vulnerabilities including HTTP response splitting, open redirect, and other issues that have been patched in subsequent versions. Fix: Update Express to the latest stable version (4.18.x or higher). Run 'npm audit' to identify and remediate all vulnerable dependencies.
  • High · Deprecated Project - No Active Maintenance — README.md - project status. According to the README, pkg has been deprecated as of version 5.8.1. The project is no longer maintained, meaning security vulnerabilities discovered after this version will not be patched. The repository is archived, indicating no future security updates will be released. Fix: Migrate to actively maintained alternatives such as Node.js 21+ native single executable applications (SEA) or other maintained forked versions of pkg.
  • Medium · Unrestricted Asset Globbing Pattern — package.json - pkg.assets. The pkg configuration uses 'views/**/*' which recursively includes all files in the views directory without filtering. This could inadvertently bundle sensitive files (configuration files, credentials, comments containing secrets) into the executable. Fix: Use explicit file patterns instead of wildcards. Exclude sensitive files and directories. Review all assets being bundled to ensure no credentials or sensitive data are included.
  • Medium · Outdated Node.js Target — package.json - pkg.targets. The pkg configuration targets 'node8', which reached end-of-life on December 31, 2019. Node 8 contains numerous security vulnerabilities that have been fixed in later versions. Fix: Update the target to a currently supported Node.js LTS version (18.x or 20.x). Node 8 is no longer receiving security patches.
  • Low · Missing Security Headers Configuration — package.json - dependencies. No visible security configuration for Express (e.g., helmet.js) in the package.json dependencies. While this is a minimal example, production Express applications should include security middleware to set appropriate HTTP headers. Fix: Add helmet.js dependency and configure it in the Express application to set security headers like Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, etc.

LLM-derived; treat as a starting point, not a security audit.

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/vercel/pkg shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live vercel/pkg repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/vercel/pkg.

What it runs against: a local clone of vercel/pkg — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in vercel/pkg | Confirms the artifact applies here, not a fork | | 2 | License is still MIT | Catches relicense before you depend on it | | 3 | Default branch main exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 887 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>vercel/pkg</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of vercel/pkg. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/vercel/pkg.git
#   cd pkg
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of vercel/pkg and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "vercel/pkg(\\.git)?\\b" \\
  && ok "origin remote is vercel/pkg" \\
  || miss "origin remote is not vercel/pkg (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
  && ok "license is MIT" \\
  || miss "license drift — was MIT at generation time"

# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
  && ok "default branch main exists" \\
  || miss "default branch main no longer exists"

# 4. Critical files exist
test -f "README.md" \\
  && ok "README.md" \\
  || miss "missing critical file: README.md"
test -f ".github/workflows/ci.yml" \\
  && ok ".github/workflows/ci.yml" \\
  || miss "missing critical file: .github/workflows/ci.yml"
test -f "LICENSE" \\
  && ok "LICENSE" \\
  || miss "missing critical file: LICENSE"
test -f ".eslintrc" \\
  && ok ".eslintrc" \\
  || miss "missing critical file: .eslintrc"
test -f ".yarnrc" \\
  && ok ".yarnrc" \\
  || miss "missing critical file: .yarnrc"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 887 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~857d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/vercel/pkg"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Embed this chat in your README →

Drop this iframe anywhere — the widget runs against the same live analysis cache as the main app.

<iframe
  src="https://repopilot.app/embed/vercel/pkg"
  width="100%" height="500"
  style="border:1px solid #d0d7de; border-radius:8px;"
  allow="microphone"
  loading="lazy"
></iframe>