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

monodrop

v0.18.0

Published

From monorepo to npm in one command

Readme

monodrop

npm version CI License: MIT

Monorepos? Great. Publishing from a monorepo? Comically hard.

The Problem

Consider @acme/my-awesome-package, which imports @acme/internal-utils, a workspace dependency. The naive approach - running npm publish - produces an uninstallable package because @acme/internal-utils was never published to npm.

The standard solution is the "publish everything" approach. Tools like Lerna will publish every internal dependency as its own public package. Installation now works, but @acme/internal-utils just became a permanently published API you're committed to maintaining. Your internal refactoring freedom is gone.

You can throw a bundler at the problem: tools like esbuild or Rollup produce a self-contained file from a given entrypoint. But type declarations and sourcemaps often break, and consumers can't tree-shake a pre-bundled blob.

The Solution

monodrop is a publishing CLI that gets this right. It produces a single publishable directory containing everything needed from your package and its in-repo dependencies. Essentially, it produces a standard npm package that looks like you hand-crafted it for publication.

  • 📦 Consumers get one package with exactly the code they need
  • 🔒 Internal packages remain unpublished
  • ✅ Tree-shaking, sourcemaps, and types all work

[!NOTE] ESM only — monodrop supports ES modules exclusively. CommonJS packages (.cjs files or .js without "type": "module") are not supported. If your monorepo uses CommonJS, consider migrating to ESM.

Quickstart

# Install
pnpm add --save-dev monodrop
# or: yarn add --dev monodrop
# or: npm install --save-dev monodrop

# Build (monodrop publishes, it doesn't build)
npm run build

# Publish
npx monodrop publish packages/my-awesome-package --bump patch

# Or use "pack" to do everything short of publishing
npx monodrop pack packages/my-awesome-package --pack-destination /tmp/inspect --bump patch

What Gets Published

Given this monorepo structure:

/path/to/my-monorepo/
└── packages/
    ├── my-awesome-package/
    │   ├── package.json      # name: @acme/my-awesome-package
    │   └── src/
    │       └── index.ts      # import ... from '@acme/internal-utils'
    └── internal-utils/
        ├── package.json      # name: @acme/internal-utils (private)
        └── src/
            └── index.ts

Running npx monodrop publish packages/my-awesome-package produces:

/tmp/monodrop-xxxxxx/
└── packages/
    └── my-awesome-package/      # preserves the package's path in the monorepo
        ├── package.json         # name: "@acme/my-awesome-package", version: "1.3.0" (the new resolved version)
        ├── dist/
        │   └── index.js         # rewritten:
        │                        # import ... from '../deps/__acme__internal-utils/dist/index.js'
        └── deps/
            └── __acme__internal-utils/  # mangled package name, exact notation may vary.
                └── dist/
                    └── index.js

The deps/ directory is where the files of in-repo dependencies get embedded. Each dependency is placed under a mangled version of its package name. This avoids name collisions regardless of where packages live in the monorepo.

Version Resolution

monodrop uses registry-based versioning: it queries the registry for the latest published version and bumps it according to your --bump flag (patch, minor, major). Your source package.json is never modified.

This means you don't need to maintain version numbers in your source code. The registry is the versioning source of truth, and monodrop computes the next version at publish time. Of course, if an exact version is specified (--bump 1.7.9) it is used as-is.

For first-time publishing (when the package doesn't exist in the registry yet), monodrop treats the current version as 0.0.0 and applies the bump—resulting in 0.0.1 for patch, 0.1.0 for minor (the default), or 1.0.0 for major.

If the version to publish to is already set in the package's package.json file (via npm version, Changesets, Lerna, etc.), you can use --bump package to read the version directly from there:

npm version minor --no-git-tag-version   # Sets version in package.json
npx monodrop publish . --bump package   # Uses that version

Examples

# --bump defaults to "minor", so these two are equivalent:
npx monodrop publish packages/my-awesome-package --bump minor
npx monodrop publish packages/my-awesome-package

# Explicit version
npx monodrop publish packages/my-awesome-package --bump 2.3.0

# Prepare everything (including the tarball) without publishing
npx monodrop pack packages/my-awesome-package --bump 2.3.0

# Package location is resolved relative to CWD
cd /path/to/my-monorepo/packages
npx monodrop publish my-awesome-package --bump 2.3.0

Programmatic API

For custom build steps, or integration with other tooling, you can use monodrop as a library instead of invoking the CLI:

import { monodrop } from 'monodrop'

const result = await monodrop({
  pathToSubjectPackages: ['packages/my-awesome-package'],
  publish: true,
  bump: 'minor',
  cwd: process.cwd()
})

console.log(result.summaries[0].version) // '1.3.0'

The above snippet is the programmatic equivalent of npx monodrop publish packages/my-awesome-package --bump minor.

Advanced Features

Custom Publish Name

Sometimes your internal package name doesn't match the name you want on npm. Add a monodrop.publishName field to your package.json to publish under a different name without renaming the package across your monorepo:

{
  "name": "@acme/my-awesome-package",
  "monodrop": {
    "publishName": "best-package-ever"
  }
}

Mirroring to a Public Repo

Want to open-source your package while keeping your monorepo private? Use --mirror-to to copy the package and its in-repo dependencies to a separate public repository:

npx monodrop publish packages/my-awesome-package --mirror-to ../public-repo

This way, your public repo stays in sync with what you publish—all necessary packages included. Contributors can clone and work on your package.

Requires a clean working tree. Only committed files (from git HEAD) are mirrored.

Multiple Packages

If you have several public packages in your monorepo, publish them in one go by listing multiple directories:

npx monodrop publish packages/lib-a packages/lib-b --bump patch

By default, each package will be published at its own version (individual versioning). If lib-a is at 1.0.0 and lib-b is at 2.0.0, a patch bump publishes them at 1.0.1 and 2.0.1 respectively.

You can also publish all specified packages at the same version (unified versioning, à la AWS SDK v3), by using the --max flag. This applies the bump to the maximum version and publishes all packages at that version.

# Now both will be published at 2.0.1 (the max)
npx monodrop publish packages/lib-a packages/lib-b --bump patch --max

This is purely a stylistic choice; correctness is unaffected since in-repo dependencies are always embedded.

Scope

monodrop makes a few deliberate choices:

  • Runtime dependencies only — Only dependencies are traversed and embedded. devDependencies are ignored since consumers don't need your build tools.
  • Version conflicts fail early — If two in-repo packages require different versions of the same third-party dependency, monodrop stops with a clear error rather than silently picking one.
  • File selection via npm pack — Your files field in package.json is the source of truth for what gets published. monodrop doesn't introduce its own file configuration.
  • Validates before heavy work — npm login and other prerequisites are checked upfront, before any file copying begins.

A few constraints to be aware of:

  • Dynamic imports must use string literals — await import('@pkg/lib') works; if a dynamic module path like await import(variable) is detected, monodrop stops with a clear error.
  • Prerelease versions require explicit --bump — --bump package expects strict semver (X.Y.Z). For prereleases, pass the version explicitly: --bump 1.0.0-beta.1.
  • peerDependencies are preserved, not embedded — As with any package publishing tool, you're responsible for ensuring peer dependencies (in-repo or not) are published and available to consumers.
  • optionalDependencies are preserved, not embedded — If you list an in-repo package as optional, you're responsible for publishing it separately.
  • Symlinks must stay within monorepo — Packages symlinked from outside the monorepo root are rejected.
  • Undeclared in-repo imports fail — If your code imports an in-repo package not listed in dependencies, monodrop catches this and fails with a clear error.

CLI Reference

monodrop publish <packages...> [options]
monodrop pack <packages...> [options]

Commands

| Command | Description | |---------|-------------| | publish | Assemble package(s) with their in-repo dependencies and publish to npm | | pack | Assemble package(s) and create tarball(s) without publishing |

Arguments

| Argument | Description | |----------|-------------| | packages | One or more package directories to assemble (required) |

Options

| Option | Alias | Type | Default | Description | |--------|-------|------|---------|-------------| | --bump | -b | string | minor | Version bump strategy: patch, minor, major, package, or explicit semver (e.g., 2.3.0). Use package to read version from package.json. | | --max | | boolean | false | Use max version across all packages (unified versioning). When false, each package uses its own version. | | --pack-destination | | string | (cwd) | pack only: directory where the tarball(s) are placed | | --root | -r | string | (auto) | Monorepo root directory (auto-detected if omitted) | | --mirror-to | -m | string | — | Mirror source files to a directory (for public repos) | | --report | | string | — | Write resolved version to a file instead of stdout | | --dynamic-imports-policy | | allow \| reject | reject | How to treat dynamic import() calls with computed (non-literal) module names: reject fails the packaging process, allow leaves them as-is | | --help | | | | Show help | | --version | | | | Show version number |

API Reference

monodrop(options): Promise<MonodropResult>

Assembles one or more monorepo packages and their in-repo dependencies, and optionally publishes to npm.

MonodropOptions

| Property | Type | Required | Default | Description | |----------|------|----------|---------|-------------| | pathToSubjectPackages | string \| string[] | Yes | — | Package directories to assemble. Relative paths resolved from cwd. | | publish | boolean | Yes | — | Whether to publish to npm after assembly. | | cwd | string | Yes | — | Base directory for resolving relative paths. | | bump | string | No | "minor" | Version specifier: "patch", "minor", "major", "package", or explicit semver. | | max | boolean | No | false | Use max version across all packages (unified versioning). | | outputRoot | string | No | (temp dir) | Output directory for the assembled package. | | monorepoRoot | string | No | (auto) | Monorepo root directory; auto-detected if omitted. | | mirrorTo | string | No | — | Mirror source files to this directory. | | npmrcPath | string | No | — | Path to .npmrc file for npm authentication. |

MonodropResult

| Property | Type | Description | |----------|------|-------------| | outputDir | string | Directory where the first package was assembled. | | resolvedVersion | string \| undefined | The unified resolved version (only set when max: true). | | summaries | Array<{ packageName: string; outputDir: string; version: string }> | Details for each assembled package, including its version. |

The Assembly Process

Here's a conceptual breakdown of the steps that happen at a typical monodrop run:

  1. Setup: Creates a dedicated output directory
  2. Version Resolution: Computes the new version (see above)
  3. Dependency Discovery: Traverses the dependency graph to find all in-repo packages the package depends on, transitively
  4. File Embedding: Copies the publishable files (per npm pack) of each in-repo dependency into the output directory
  5. Entry Point Resolution: Examines each package's entry points (respecting exports and main fields) to compute the exact file locations that import statements will resolve to
  6. Import Rewriting: Scans the .js and .d.ts files, converting imports of workspace packages to relative path imports (@acme/internal-utils becomes ../deps/__acme__internal-utils/dist/index.js)
  7. Package.json Rewrite: Sets the resolved version, removes in-repo deps, and adds any third-party deps they brought in