Skip to content

require.main === module is true in EVERY compiled CommonJS module, so any package with a CLI entry guard runs its CLI branch when merely imported #10735

Description

@proggeramlug

require.main === module is true inside every compiled CommonJS module, not just the process entry point.

crates/perry/src/commands/compile/cjs_wrap/wrap.rs:1138 — Perry's CJS preamble unconditionally emits
require.main = module; for every compiled CommonJS module. So the standard entry-point check is trivially
true everywhere.

Minimal repro (no dependencies)

A throwaway CJS package containing console.log(require.main === module), required from another file:

  • Node: false
  • Perry: true

Why this matters more than it looks

if (require.main === module) { … } is the CommonJS idiom for "am I being run directly, or merely imported?".
Packages use it to decide whether to run CLI behaviour — parse process.argv, print usage, call process.exit().
Under Perry every such package takes the CLI branch when it is merely imported as a library.

Observed concretely: with the native dotenv binding removed and real dotenv@18.0.1 compiled from source, a plain
import dotenv from "dotenv" prints dotenv's bundled CLI usage banner and the process exits 1 instead of 0
— purely from this. The library functions themselves work correctly.

This was masked, and that is the interesting part

The defect is general and pre-existing, not introduced by any recent change. It was invisible because the native
dotenv binding intercepted the import, so the real npm source was never compiled
. Removing the binding is what
first exercises the path.

That is worth noting for the binding-removal campaign generally (#10678 and the removal PRs): every binding we
delete stops masking whatever compiler defects the real package's code exercises. This is the second finding of
that shape today — the first being agent-base's TypeScript namespace + export = handling, surfaced by
compiling real axios (#10662 → PR #10673). Expect more, and treat them as latent debt being uncovered rather
than as regressions caused by the removals.

Fix direction

require.main should be set to the entry module only, so it compares equal in the process entry point and
unequal everywhere else. The preamble needs to distinguish the entry module from a required dependency — that
distinction already exists elsewhere in the compile path, since Perry knows which module is the entry.

Found while rebasing PR #10691 (remove dotenv native binding); not fixed there, since it is a compiler-wide CJS
defect rather than anything dotenv-specific.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    package-auditFound by the 2026 package audit: compiling real npm packages from source instead of native bindings

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions