diff --git a/.changeset/fix-copytraced-package-json-suffix.md b/.changeset/fix-copytraced-package-json-suffix.md
new file mode 100644
index 000000000..32b6f5fd9
--- /dev/null
+++ b/.changeset/fix-copytraced-package-json-suffix.md
@@ -0,0 +1,10 @@
+---
+"@opennextjs/aws": patch
+---
+
+`copyTracedFiles`: only register a traced file as a node package when its
+basename is exactly `package.json`, not when its filename merely ends with
+`package.json` (e.g. `care-package.json`). The previous suffix-only check
+caused harmless but noisy `Failed to copy
` errors in the Cloudflare
+adapter's workerd-package copy step whenever a project traced a JSON data
+file whose name happened to end in `-package.json`.
diff --git a/packages/open-next/src/build/copyTracedFiles.ts b/packages/open-next/src/build/copyTracedFiles.ts
index 9f6689c0f..6becc9129 100644
--- a/packages/open-next/src/build/copyTracedFiles.ts
+++ b/packages/open-next/src/build/copyTracedFiles.ts
@@ -85,6 +85,16 @@ export function isNonLinuxPlatformPackage(srcPath: string): boolean {
return nonLinuxPlatformRegex.test(srcPath);
}
+// `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";
+}
+
function copyPatchFile(outputDir: string) {
const patchFile = path.join(__dirname, "patch", "patchedAsyncStorage.js");
const outputPatchFile = path.join(outputDir, "patchedAsyncStorage.cjs");
@@ -153,7 +163,7 @@ export async function copyTracedFiles({
filesToCopy.set(src, dst);
const module = path.join(dotNextDir, subDir, tracedPath);
- if (module.endsWith("package.json")) {
+ if (isPackageJson(module)) {
nodePackages.set(path.dirname(module), path.dirname(dst));
}
});
diff --git a/packages/tests-unit/tests/build/copyTracedFiles.test.ts b/packages/tests-unit/tests/build/copyTracedFiles.test.ts
index 814c822b7..7644cdc88 100644
--- a/packages/tests-unit/tests/build/copyTracedFiles.test.ts
+++ b/packages/tests-unit/tests/build/copyTracedFiles.test.ts
@@ -1,6 +1,7 @@
import {
isExcluded,
isNonLinuxPlatformPackage,
+ isPackageJson,
} from "@opennextjs/aws/build/copyTracedFiles.js";
describe("isExcluded", () => {
@@ -129,3 +130,27 @@ describe("isNonLinuxPlatformPackage", () => {
).toBe(false);
});
});
+
+describe("isPackageJson", () => {
+ test("should detect package.json", () => {
+ expect(isPackageJson("/project/node_modules/next/package.json")).toBe(true);
+ expect(isPackageJson("package.json")).toBe(true);
+ });
+
+ test("should not treat files whose names end with 'package.json' as package.json", () => {
+ // Regression: traced JSON data files in user code can have names like
+ // `care-package.json` that share the "package.json" suffix without being
+ // real package manifests. The previous `endsWith("package.json")` check
+ // mis-classified them, causing `Failed to copy ` errors in the
+ // Cloudflare adapter's workerd-package copy step.
+ expect(isPackageJson("/project/data/tables/care-package.json")).toBe(false);
+ expect(isPackageJson("/project/fixtures/my-package.json")).toBe(false);
+ });
+
+ test("should handle Windows-style path separators", () => {
+ expect(isPackageJson("C:\\project\\node_modules\\next\\package.json")).toBe(
+ true,
+ );
+ expect(isPackageJson("C:\\project\\data\\care-package.json")).toBe(false);
+ });
+});