From 8e2fb0432973637f814429e550b97478ab607761 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Fri, 28 Aug 2026 05:29:52 -0300 Subject: [PATCH] fix(build): drop Next trace manifests from the npm tarball (413 on publish) (#11864) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v3.8.50 staged publish was refused by the registry: npm error code E413 npm error 413 Payload Too Large - POST https://registry.npmjs.org/-/stage/package/omniroute The tarball had reached 288.7 MB packed / 1.1 GB unpacked, against 174.5 MB / 792.3 MB for the 3.8.49 that published fine. 842 *.nft.json files accounted for 668.7 MB of that — 61% of the whole package — having doubled from the 325.0 MB across 748 files shipped in 3.8.49. Those are Next.js Node File Trace manifests: build-time metadata used to compute the standalone bundle, never read while serving. Nothing under src/, open-sse/ or bin/ references them, which the new test pins. Excluding them follows the negation pattern files[] already uses for node_modules and test sources. Verified against an isolated package that the glob drops page.js.nft.json while keeping page.js and other.json, so it does not over-match. Separately worth tracking: the compiled JS under dist/.build/next also grew 69% between 3.8.49 and 3.8.50 (231.9 MB to 391.9 MB, +3695 files). That is not what broke the publish and is left for its own investigation. --- .../fixes/11856-npm-payload-nft-manifests.md | 4 ++ package.json | 3 +- ...npm-payload-nft-manifests-excluded.test.ts | 72 +++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 changelog.d/fixes/11856-npm-payload-nft-manifests.md create mode 100644 tests/unit/npm-payload-nft-manifests-excluded.test.ts diff --git a/changelog.d/fixes/11856-npm-payload-nft-manifests.md b/changelog.d/fixes/11856-npm-payload-nft-manifests.md new file mode 100644 index 0000000000..0420d6f9c0 --- /dev/null +++ b/changelog.d/fixes/11856-npm-payload-nft-manifests.md @@ -0,0 +1,4 @@ +- Excluded Next.js Node File Trace manifests (`*.nft.json`) from the published npm + tarball. They are build-time metadata and are never read while serving, but had + grown to 668.7 MB — 61% of the package — which pushed the upload past the + registry limit and made `npm publish` fail with `413 Payload Too Large`. diff --git a/package.json b/package.json index 54a0bc149e..152f897ce2 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,8 @@ "!**/*.test.js", "!**/*.test.mjs", "!**/*.spec.ts", - "!**/*.spec.tsx" + "!**/*.spec.tsx", + "!**/*.nft.json" ], "workspaces": [ "open-sse", diff --git a/tests/unit/npm-payload-nft-manifests-excluded.test.ts b/tests/unit/npm-payload-nft-manifests-excluded.test.ts new file mode 100644 index 0000000000..4ab0ff0936 --- /dev/null +++ b/tests/unit/npm-payload-nft-manifests-excluded.test.ts @@ -0,0 +1,72 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync, readdirSync } from "node:fs"; +import type { Dirent } from "node:fs"; +import { join } from "node:path"; + +/** + * v3.8.50 was refused by the registry with `413 Payload Too Large` on + * `POST /-/stage/package/omniroute`: the tarball had reached 288.7 MB packed + * (1.1 GB unpacked), against 174.5 MB for the 3.8.49 that published fine. + * + * 668.7 MB of that — 61% of the whole package — was 842 `*.nft.json` files. + * Those are Next.js Node File Trace manifests: build-time metadata used to + * COMPUTE the standalone bundle, never read while serving. They had doubled + * since 3.8.49 (325.0 MB across 748 files), which is what tipped the payload + * over the limit. + * + * The guard is the `files[]` negation, so a future entry that re-widens the + * glob (or a rewrite of the array) cannot silently put them back. + */ +const pkg = JSON.parse(readFileSync(join(import.meta.dirname, "../../package.json"), "utf8")) as { + files?: string[]; +}; + +test("package.json files[] excludes Next's .nft.json trace manifests", () => { + const files = pkg.files ?? []; + assert.ok(files.length > 0, "package.json must declare files[]"); + assert.ok( + files.includes("!**/*.nft.json"), + "files[] must negate **/*.nft.json — they are build metadata and were 61% of the 3.8.50 payload" + ); +}); + +test("the negation sits after the positive dist/ entry it has to override", () => { + // npm applies files[] in order: a negation listed BEFORE the directory that + // pulls the files in is a no-op. Positive anchor, so this test cannot pass + // just because both strings happen to be present somewhere. + const files = pkg.files ?? []; + const dist = files.indexOf("dist/"); + const negation = files.indexOf("!**/*.nft.json"); + assert.notEqual(dist, -1, "dist/ must still be published"); + assert.ok(negation > dist, "the .nft.json negation must come after dist/"); +}); + +test("no source module reads a .nft.json at runtime", () => { + // If this ever stops holding, the exclusion above becomes a runtime break + // rather than a size win — which is exactly the assumption worth pinning. + const roots = ["src", "open-sse", "bin"]; + const hits: string[] = []; + for (const root of roots) { + const dir = join(import.meta.dirname, "../..", root); + const stack = [dir]; + while (stack.length > 0) { + const current = stack.pop() as string; + let entries: Dirent[]; + try { + entries = readdirSync(current, { withFileTypes: true }); + } catch { + continue; + } + for (const entry of entries) { + const full = join(current, entry.name); + if (entry.isDirectory()) { + if (entry.name !== "node_modules") stack.push(full); + } else if (/\.(ts|tsx|mjs|js)$/.test(entry.name)) { + if (readFileSync(full, "utf8").includes(".nft.json")) hits.push(full); + } + } + } + } + assert.deepEqual(hits, [], `nothing may depend on .nft.json at runtime: ${hits.join(", ")}`); +});