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

edge-compat-check

v1.0.0

Published

Static-analysis dependency scanner for edge runtime compatibility — scans your Node.js dependencies for Node.js APIs that break on Vercel Edge, Cloudflare Workers, Netlify Edge, and Fastly Compute, using per-target profiles you can run locally or as a CI

Readme

edge-compat-check

Static-analysis dependency scanner for Node.js APIs that break on edge runtimes.

version license: MIT node

Developed and open-sourced by Nextbridge.


edge-compat-check is a static analysis scanner that checks whether your dependencies use Node.js APIs unavailable on Vercel Edge, Cloudflare Workers, Netlify Edge Functions, and Fastly Compute — reporting what breaks, why, and which Web API to use instead. With --ci, it exits with code 1 if incompatible APIs are found, failing the pipeline before a broken deploy ships.

Contents

Quick Start

npx edge-compat-check

No install required — see Example output for a full run, or jump to CI integration to gate a pipeline on the result.

Features

  • Per-target compatibility profilesvercel-edge, cloudflare-workers, netlify-edge, fastly-compute, strict.
  • Direct or deep scanning — full transitive tree via lockfile (npm, pnpm, Yarn Classic).
  • CI-ready--ci exits non-zero when an incompatible API is found.
  • Machine-readable output--json for custom tooling and dashboards.
  • Fast — concurrent scanning (8 workers) across large dependency trees.
  • Configurable--ignore to suppress specific Node core modules, --include-dev to scan devDependencies.
  • Safe by construction — path-traversal protection and symlink-safe file discovery, with support for pnpm's linked node_modules layout (pnpm's deep lockfile scanning itself remains best-effort — see Limitations).
  • Broad file coverage — scans .js, .mjs, .cjs, .jsx, .ts, .mts, .cts, .tsx source.
  • TypeScript-awareimport type and type-only export type ... from re-exports are ignored, so they don't produce false-positive findings.
  • Bounded scans — 2 MB per-file size cap, 80-file default cap per dependency, so runs stay fast on large trees.
  • Deterministic output — stable ordering, diff-friendly in CI logs.
  • Zero runtime dependencies, with TypeScript declarations for the programmatic API.

Why & Who It's For

Edge runtimes are not equal — Vercel Edge supports only a small documented set of Node.js APIs, while Cloudflare Workers (with nodejs_compat) supports most of them. A single hard-coded "unsafe module" list tuned for one target produces false positives on another, or false negatives.

edge-compat-check models each target as its own profile, checked against each vendor's compatibility docs (see src/profiles.js for sources and verification dates).

Use it if you're deploying to Vercel Edge, Cloudflare Workers, Netlify Edge, or Fastly Compute; auditing a new dependency for edge-safety; gating CI with a fast, zero-dependency check; or a library author avoiding accidental Node-only imports.

Supported runtimes

| Target | Runtime | Node.js support | | ----------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------- | | vercel-edge (default) | V8 isolate | Small documented set (assert, async_hooks, buffer, events, util) via import only — require() unsupported | | cloudflare-workers | V8 + nodejs_compat | Large subset (date-gated) | | netlify-edge | Deno | Via node: prefix (required); gaps in OS/threading | | fastly-compute | WASM (js-compute) | None (WinterTC Web APIs only) | | strict | — | None (assume zero Node support) |

Vercel Edge: assert, async_hooks, buffer, events, and util are supported when loaded via import (with or without the node: prefix). Every other Node core module is unsupported. A supported module still fails when loaded via require(), since Vercel Edge requires ES modules.

Netlify Edge: Node built-ins must be imported with the node: prefix (e.g. import "node:crypto"). A bare specifier (import "crypto") fails, with a suggestion to add the prefix, even for an otherwise-supported module. require() of a Node built-in is unsupported regardless of prefix.

Supported package managers

--deep resolves the full transitive tree from whichever lockfile is present:

| Package manager | Lockfile | Deep-scan support | | --------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | npm | package-lock.json | Full — v6 dependencies tree and v7+ packages map | | Yarn Classic | yarn.lock | Full — walks the installed node_modules tree, so root, nested, scoped, and duplicate physical installations of a locked package are all discovered and scanned | | pnpm | pnpm-lock.yaml | Best-effort lockfile parsing (regex, not a full YAML parse; tested against pnpm lockfileVersion 5-9) — every matching physical .pnpm store directory is expanded and scanned, so distinct installed versions of the same package are scanned separately | | Yarn Berry | yarn.lock (Berry) | May be incomplete — Yarn Classic format is what's fully supported | | Bun | bun.lockb | Not supported — --deep falls back to a direct (non-transitive) scan and emits a warnings entry | | Yarn PnP | .pnp.cjs/.pnp.js | Not supported — packages aren't extracted to node_modules, so most can't be resolved; emits a warnings entry |

When a deep scan finds more than one physical installation of a dependency (e.g. two pnpm-resolved versions, or a nested Yarn Classic copy), each installation is reported as its own result carrying its version and location — see JSON output.

Without --deep (or if no lockfile is found), the scanner reads direct dependencies straight from package.json — no lockfile parsing needed.

Installation

No install required — see Quick Start. For repeatable use in CI, install as a dev dependency:

npm install -D edge-compat-check
npx edge-compat-check --target vercel-edge --ci

Requires Node.js >= 18. Zero runtime dependencies.

Usage

npx edge-compat-check                                   # vercel-edge, direct deps
npx edge-compat-check --target cloudflare-workers       # different runtime
npx edge-compat-check ./apps/web --target netlify-edge  # subdirectory
npx edge-compat-check --deep                            # transitive tree
npx edge-compat-check --ci                              # exit 1 on failure
npx edge-compat-check --json > report.json              # machine-readable
npx edge-compat-check --ignore crypto,fs                # suppress modules

CLI options

| Flag | Description | | -------------------- | ------------------------------------------------------ | | [path] | Project directory to scan (default: current directory) | | --target <name> | Compatibility profile (default: vercel-edge) | | --deep | Scan full transitive tree via lockfile | | --include-dev | Include devDependencies | | --ignore <modules> | Comma-separated modules to skip | | --ci | Exit code 1 if incompatible APIs found | | --json | JSON output | | --no-suggestions | Hide suggestions | | --no-color | Disable colors | | -v, --version | Show version | | -h, --help | Show help |

Unknown flags exit with code 2 so misspelled CI commands fail fast.

Note: --ignore suppresses findings for the specified module across the entire scan. Use it only for confirmed false positives, not to hide genuine compatibility issues.

Example output

A run with one incompatible dependency:

npx edge-compat-check v1.0.0  ·  3 deps  ·  direct
target: Vercel Edge Runtime (Middleware)

✗ some-pkg
    imports "crypto"
      "crypto" is not supported on Vercel Edge Runtime (Middleware).
      → Use the Web Crypto API (globalThis.crypto / crypto.subtle).

Result: 1 dep(s) may break on Vercel Edge Runtime (Middleware)

A clean run:

npx edge-compat-check v1.0.0  ·  12 deps  ·  direct
target: Vercel Edge Runtime (Middleware)

✓ No incompatible APIs found for this target.

Result: PASS

A dependency that hits the file scan cap is flagged as a warning, not a failure — see Limitations:

⚠ some-large-pkg
    [Warning] Exceeded the 80 file limit. Some files were not scanned.

JSON output

--json writes a stable, machine-readable object. This is the actual output from scanning this dependency-free package:

{
  "version": "1.0.0",
  "timestamp": "2026-07-15T08:31:25.639Z",
  "target": "Vercel Edge Runtime (Middleware)",
  "mode": "direct",
  "warnings": [],
  "totalDeps": 0,
  "scanned": 0,
  "flagged": [],
  "skipped": []
}

version and location are optional and only present when the scanner can attach an identity to the result — most commonly on a --deep scan where pnpm or Yarn Classic resolved more than one physical installation of the same dependency (see Supported package managers). location is a repository-relative path to the physical package directory that produced the result, so duplicate installations of the same dependency name are distinguishable in both flagged and skipped.

mode is one of "direct", "deep" (lockfile resolved successfully), or "direct-no-lockfile" (--deep was requested but no supported lockfile was found, so the scan fell back to direct dependencies).

warnings is a top-level array of strings, always present (empty when there's nothing to report). It's populated for things like an unrecognized pnpm-lock.yaml version, a corrupt package-lock.json, or the Bun/Yarn PnP fallbacks described in Supported package managers.

skipped[].reason is one of four values:

| Reason | Meaning | | ------------------- | -------------------------------------------------------------------------------------- | | notInstalled | Dependency isn't present in node_modules | | unresolvedLayout | Deep scan couldn't map the dependency to a location on disk | | workspaceSkipped | A workspace/link: reference couldn't be resolved | | boundaryViolation | Resolving the dependency would follow a symlink outside the project/workspace boundary |

Note: an entry appears in flagged if it has findings or if hitLimit is true — those aren't the same thing. hitLimit: true only means the 80-file scan cap was reached before the whole package could be scanned; it is not itself a compatibility failure. The CLI's own --ci exit code correctly only fails on real findings. If you build pass/fail logic on top of this JSON, filter on findings.length > 0, not just presence in flagged.

Exit codes

| Code | Meaning | | ---- | --------------------------------------------------------------------------------- | | 0 | Scan completed. Also used when findings exist unless --ci is set. | | 1 | --ci was set and one or more dependencies had incompatible API findings. | | 2 | Invalid arguments, unknown flags, unknown target profile, or package read errors. |

CI integration

name: edge-compat
on: [push, pull_request]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci
      - run: npx edge-compat-check --target vercel-edge --deep --ci

Programmatic API

import { scanProject, formatReport } from "edge-compat-check";

const scan = await scanProject("./", {
  profile: "cloudflare-workers",
  deep: true,
  ignore: new Set(["crypto"]),
});

console.log(formatReport(scan));

scanProject(dir, opts)Promise<ScanResult>

| Option | Type | Default | Description | | ------------- | ------------- | -------------------------------------- | -------------------------------------------- | | profile | string | "vercel-edge" | Target profile | | deep | boolean | false | Scan transitive tree | | includeDev | boolean | false | Include devDependencies | | filesPerDep | number | 80 | Max files to scan per dependency | | ignore | Set<string> | — | Modules to skip | | boundary | string | the project's node_modules directory | Boundary path for lockfile/symlink traversal | | onProgress | function | — | (depName, done, total) => void |

formatReport(scan, opts)string

Renders a ScanResult as the same human-readable report the CLI prints. opts.showSuggestions (default true) and opts.color (default true) mirror the CLI's --no-suggestions / --no-color flags.

toJSON(scan)string

Renders a ScanResult as the same JSON string the CLI prints with --json — see JSON output for the schema.

TypeScript support

Type declarations ship in src/index.d.ts and are resolved automatically via the package's exports map — no @types package needed.

import type {
  ScanResult,
  DependencyResult,
  Finding,
  ScanProjectOptions,
} from "edge-compat-check";

| Export | Kind | Notes | | ---------------------------------------------------------------------------- | ------------------------- | --------------------------------------------------------- | | scanProject | function | See Programmatic API | | formatReport | function | See Programmatic API | | toJSON | function | See Programmatic API | | PROFILES | Record<string, Profile> | The built-in compatibility profiles, keyed by target name | | NODE_CORE_MODULES | string[] | Every Node.js core module name the scanner recognizes | | Finding, DependencyResult, ScanResult, ScanProjectOptions, Profile | types | Public shapes for the API above |

Note: a Finding from scanProject() has a suggest field. The CLI's --json output (and toJSON()) renames it to suggestion for readability — see the schema in JSON output. If you consume scanProject() directly rather than toJSON(), read finding.suggest, not finding.suggestion.

Note: DependencyResult has optional version and location fields, populated the same way as the JSON output's flagged[]/skipped[] entries — see JSON output.

How It Works

  1. Collects dependency names from package.json (direct) or a lockfile (transitive, via --deep — see Supported package managers).
  2. Statically scans each installed package for imports/requires of Node core modules.
  3. Checks each import against the target's compatibility profile (see Supported runtimes).
  4. Reports findings with reasons and suggested Web API alternatives.
  5. In --ci mode, exits with code 1 if an incompatible API is found — so the build fails before the deploy does.

Limitations

  • Lexical, not AST-based. No code is executed and no JavaScript/TypeScript parser is used; guarded imports (e.g. behind a runtime check) may be false positives, and dynamically constructed import specifiers (e.g. built from a variable) can't be detected.
  • Conditional exports and the reachable import graph aren't resolved. The scanner reads every scanned source file's imports rather than following package.json exports conditions or which files are actually reachable at runtime, so an inactive platform-specific file can still produce a false positive.
  • 80-file default scan cap per dependency (filesPerDep, configurable via the programmatic API). See the hitLimit note in JSON output.
  • Lockfile support varies by package manager — see Supported package managers.
  • Profiles drift. Edge runtimes evolve fast; treat results as a signal, not a guarantee. See src/profiles.js for verification dates and sources.
  • Requires node_modules. Run after npm ci / pnpm install / yarn install.
  • Nested template literals in source may cause rare false positives/negatives during comment/string stripping.

Troubleshooting

It says "No incompatible APIs found" but I expected findings. Confirm the dependency was actually scanned, not skipped — see skipped[].reason in JSON output. The import may also be behind a runtime guard (static analysis can't evaluate conditions), or the dependency may have hit the scan cap (hitLimit: true).

A command exits with code 2 — what does that mean? Code 2 means invalid usage: an unknown flag, a missing/unknown --target profile, or a package.json that couldn't be read. It's not a compatibility failure — check the flag spelling against --help.

--ci didn't fail the build even though hitLimit was true. That's expected — see the note in JSON output. Only real findings fail --ci; a scan cap warning alone does not.

FAQ

Does this replace testing on the actual edge runtime? No — it's a static, import-based pre-flight signal, not a substitute for a real deploy or preview build.

Which package managers are supported? See Supported package managers for the full breakdown by lockfile.

Can I scan a subdirectory in a monorepo? Yes — pass the path as the first argument, e.g. edge-compat-check ./apps/web --target cloudflare-workers.

Does it work with TypeScript projects? Yes — .ts, .mts, .cts, and .tsx source files are scanned like any other, and import type / type-only export type ... from declarations are ignored so they don't produce false positives. The programmatic API also ships its own type declarations; see TypeScript support.

Can I use this outside the CLI, e.g. in a custom script or dashboard? Yes — see Programmatic API for scanProject, formatReport, and toJSON.

Changelog

Initial release. See Features above.

License

MIT © Nextbridge


Built and maintained by Nextbridge

GitHub · npm · Issues