Skip to content

fix(build): use basename to detect package.json in copyTracedFiles#1166

Open
ash1day wants to merge 1 commit into
opennextjs:mainfrom
ash1day:fix/copytraced-package-json-suffix
Open

fix(build): use basename to detect package.json in copyTracedFiles#1166
ash1day wants to merge 1 commit into
opennextjs:mainfrom
ash1day:fix/copytraced-package-json-suffix

Conversation

@ash1day

@ash1day ash1day commented May 12, 2026

Copy link
Copy Markdown

What

copyTracedFiles decides whether a traced file is a node-package manifest with

if (module.endsWith("package.json")) {
  nodePackages.set(path.dirname(module), path.dirname(dst));
}

endsWith matches any filename whose tail is package.json, so files like care-package.json or my-package.json get registered as if their parent directory were a node package. The map is then handed to the Cloudflare adapter's copyWorkerdPackages, which tries to read <parent-dir>/package.json and surfaces:

ERROR Failed to copy /path/to/data/tables

The actual file copy is unaffected — that goes through filesToCopy, which is keyed by full paths — so the error is purely cosmetic. But it's misleading enough that someone hitting it usually has to dig into OpenNext's source to convince themselves the deploy is fine, which is what motivated this patch.

Repro

In a Next.js app, statically import a JSON data file whose name ends in -package.json:

// src/lib/foo.ts
import careData from '@@/data/tables/care-package.json' assert { type: 'json' };

Next traces that file, OpenNext walks the .nft.json, and the inline endsWith check fires on care-package.json. Cloudflare adapter's deploy log then prints Failed to copy <dir> for each affected directory.

Fix

Switch the inline check to a real basename comparison. The helper is exported so the regression is covered by a unit test alongside isExcluded / isNonLinuxPlatformPackage.

export function isPackageJson(modulePath: string): boolean {
  const lastSep = Math.max(
    modulePath.lastIndexOf("/"),
    modulePath.lastIndexOf("\\"),
  );
  return modulePath.slice(lastSep + 1) === "package.json";
}

path.basename was avoided because traced paths can come from either platform after path.join, and path.basename only splits on the OS-native separator. The manual scan handles both.

Tests

Added three cases in packages/tests-unit/tests/build/copyTracedFiles.test.ts:

  • positive: …/next/package.json and bare package.jsontrue
  • regression: …/care-package.json, …/my-package.jsonfalse
  • cross-platform: Windows-style \ separators behave the same
✓ packages/tests-unit/tests/build/copyTracedFiles.test.ts (12 tests) 3ms

Risk

Behavior only changes for filenames that share the package.json suffix but aren't actually package manifests. Real package.json traces continue to be picked up identically.

Changeset

Included a patch changeset under .changeset/fix-copytraced-package-json-suffix.md.

`copyTracedFiles` registered every traced file whose path ended with
"package.json" as a node package. This mis-classified user data files
whose names happen to share that suffix (e.g. `care-package.json`),
causing the Cloudflare adapter's workerd-package copy step to log
`Failed to copy <parent-dir>` errors when it tried to read a sibling
`package.json` that did not exist.

The actual file copy goes through `filesToCopy`, so this was always a
noisy log rather than a real failure. Switching to a basename check
removes the false positive entirely.
@changeset-bot

changeset-bot Bot commented May 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 646b685

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@opennextjs/aws Patch
app-pages-router Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@sommeeeer sommeeeer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@vicb vicb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess using path.basename could be a proper fix?

Comment on lines +88 to +97
// `path.basename` only splits on the OS-native separator, so we explicitly
// look at both `/` and `\` to handle paths produced under either platform.
export function isPackageJson(modulePath: string): boolean {
const lastSep = Math.max(
modulePath.lastIndexOf("/"),
modulePath.lastIndexOf("\\"),
);
return modulePath.slice(lastSep + 1) === "package.json";
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid using OS specific constructs as much as possible.
I don't think this is needed here.


const module = path.join(dotNextDir, subDir, tracedPath);
if (module.endsWith("package.json")) {
if (isPackageJson(module)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would that work instead?

Suggested change
if (isPackageJson(module)) {
if (path.basename(module) === "package.json") {

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.

3 participants