Skip to content

fix(payload): use named import for loadEnvConfig to fix CJS interop under raw tsx#16630

Open
chae4chae wants to merge 1 commit into
payloadcms:mainfrom
chae4chae:fix/loadenv-named-import
Open

fix(payload): use named import for loadEnvConfig to fix CJS interop under raw tsx#16630
chae4chae wants to merge 1 commit into
payloadcms:mainfrom
chae4chae:fix/loadenv-named-import

Conversation

@chae4chae
Copy link
Copy Markdown

Summary

@next/env is pure CJS (no "type", no "exports", just main: "dist/index.js") and emits its API as named exportsloadEnvConfig is not available on a default export. Today's code in loadEnv.ts:

import nextEnvImport from '@next/env'
const { loadEnvConfig } = nextEnvImport

only typechecks because of esModuleInterop, and only works at runtime under bundlers (Next, esbuild with synthetic-default enabled) that synthesize a default for CJS modules. Under raw tsx, nextEnvImport is undefined and the top-level destructure throws:

TypeError: Cannot destructure property 'loadEnvConfig' of 'import_env.default' as it is undefined.
  at .../payload/dist/bin/loadEnv.js:3:9

Switching to a named import — import { loadEnvConfig } from '@next/env' — matches the package's actual .d.ts export shape (export declare function loadEnvConfig(...)), removes the now-unnecessary destructure indirection, and works under both Next's bundler and raw tsx. No behavior change otherwise.

Reproduction

Any script that imports payload from outside Next.js — e.g. a seed/migration script following the documented getPayload({ config }) pattern — fails when invoked via tsx scripts/<name>.ts. The crash is reached transitively: the Postgres adapter pulls in @payloadcms/drizzle/dist/utilities/blocksToJsonMigrator.js, which top-level imports payload/node, which re-exports loadEnv from bin/loadEnv.js. So loadEnv.js evaluates eagerly and the top-level destructure throws before any user code runs.

Verification

Tested downstream by applying this exact change via pnpm patch on Payload v3.78.0 in a separate project. After patching, getPayload({ config }) initializes cleanly under tsx scripts/<name>.ts and the script proceeds through schema pull, DB connection, and seed logic without further errors. Same source still works inside Next.js (no behavior change for the normal path).

Diff

-import nextEnvImport from '@next/env'
+import { loadEnvConfig } from '@next/env'

 import { findUpSync } from '../utilities/findUp.js'
-const { loadEnvConfig } = nextEnvImport

Net: 1 file, +1/-2 lines.

…nder raw tsx

`@next/env` is pure CJS (no `"type"`, no `"exports"`, just
`main: "dist/index.js"`) and emits its API as named exports
(see `@next/env`'s `dist/index.d.ts`: `export declare function
loadEnvConfig(...)`). There is no real default export.

The current code:

  import nextEnvImport from '@next/env'
  const { loadEnvConfig } = nextEnvImport

only typechecks because of `esModuleInterop`, and only works at
runtime under bundlers (Next, esbuild with synthetic-default
enabled) that synthesize a default for CJS modules. Under raw
`tsx` invocation, `nextEnvImport` is undefined and the top-level
destructure throws:

  TypeError: Cannot destructure property 'loadEnvConfig'
    of 'import_env.default' as it is undefined.
      at .../payload/dist/bin/loadEnv.js:3:9

This bricks every script that imports `payload` from outside
Next.js — including projects' own seed/migration scripts that
follow Payload v3's documented `getPayload({ config })` pattern,
because `loadEnv.js` is loaded transitively via
`payload/node` → `@payloadcms/drizzle/dist/utilities/blocksToJsonMigrator.js`
(or equivalent in the Mongo adapter).

Switching to a named import — `import { loadEnvConfig } from '@next/env'` —
matches the package's actual export shape, removes the unnecessary
destructure indirection, and works under both Next's bundler and
raw `tsx`. No behavior change otherwise.

Tested downstream by patching this file via `pnpm patch` in a
project; `getPayload()` then succeeds end-to-end under
`tsx scripts/<name>.ts`.
@github-actions
Copy link
Copy Markdown
Contributor

Pull Request titles must follow the Conventional Commits specification and have valid scopes.

Unknown scope "payload" found in pull request title "fix(payload): use named import for loadEnvConfig to fix CJS interop under raw tsx". Scope must match one of: cpa, claude, codemod, db-*, db-d1-sqlite, db-mongodb, db-postgres, db-vercel-postgres, db-sqlite, db-d1-sqlite, drizzle, email-*, email-nodemailer, email-resend, eslint, evals, graphql, kv, kv-redis, live-preview, live-preview-react, live-preview-vue, next, payload-cloud, plugin-cloud, plugin-cloud-storage, plugin-ecommerce, plugin-form-builder, plugin-import-export, plugin-mcp, plugin-multi-tenant, plugin-nested-docs, plugin-redirects, plugin-search, plugin-sentry, plugin-seo, plugin-stripe, richtext-*, richtext-lexical, sdk, skills, storage-*, storage-azure, storage-gcs, storage-r2, storage-uploadthing, storage-vercel-blob, storage-s3, translations, ui, templates, examples(/(\w|-)+)?, deps.

feat(ui): add Button component
^    ^    ^
|    |    |__ Subject
|    |_______ Scope
|____________ Type

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant