fix(externals): force-trace named traceDeps to fix pnpm nested deps#4391
fix(externals): force-trace named traceDeps to fix pnpm nested deps#4391pi0x wants to merge 16 commits into
Conversation
Pass the resolved named `traceDeps` (builtins + user, RegExp excluded) to nf3's `traceNodeModules` via the new `traceInclude` option. nft cannot statically detect dynamically-loaded packages (e.g. native bindings), and under pnpm such a nested dependency is not hoisted — it only resolves from the dependent package's real `.pnpm` location, which nf3 now handles. Requires nf3 with `traceInclude` support (unjs/nf3#50). Closes #4372
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe PR updates trace-dependency collection, removes deno-specific ChangesTrace dependency forwarding
ServeStatic deno removal
Vercel function directory copy
OpenAPI fixture and regression coverage
Documentation and dependency refresh
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/unit/trace-deps.test.ts`:
- Around line 43-50: The trace deps test is too loose because it only checks
that traceInclude entries are strings, which would still pass if the RegExp
selector were incorrectly stringified. Tighten the assertion in
trace-deps.test.ts around resolveTraceDeps to verify the exact traceInclude
membership and count for the ["my-pkg", /my-.*-pkg/] case, using the existing
defaults fixture and the resolveTraceDeps result to ensure only the expected
builtins plus "my-pkg" are present.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d67ddb6c-1c92-46e6-a3e8-a797f95e462a
📒 Files selected for processing (2)
src/build/plugins/externals.tstest/unit/trace-deps.test.ts
| it("returns named deps as traceInclude (builtins + user, RegExp excluded)", () => { | ||
| const result = resolveTraceDeps(["my-pkg", /my-.*-pkg/], defaults); | ||
| expect(result.traceInclude).toContain("sharp"); | ||
| expect(result.traceInclude).toContain("canvas"); | ||
| expect(result.traceInclude).toContain("my-pkg"); | ||
| // RegExp entries cannot be resolved by name and must be excluded | ||
| expect(result.traceInclude!.every((d) => typeof d === "string")).toBe(true); | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Assert the exact traceInclude membership.
This test doesn't really prove the RegExp selector is excluded. An implementation that accidentally stringifies the regex would still pass typeof d === "string". Please assert the final members/count instead.
Suggested test tightening
it("returns named deps as traceInclude (builtins + user, RegExp excluded)", () => {
const result = resolveTraceDeps(["my-pkg", /my-.*-pkg/], defaults);
- expect(result.traceInclude).toContain("sharp");
- expect(result.traceInclude).toContain("canvas");
- expect(result.traceInclude).toContain("my-pkg");
- // RegExp entries cannot be resolved by name and must be excluded
- expect(result.traceInclude!.every((d) => typeof d === "string")).toBe(true);
+ expect(result.traceInclude).toEqual(
+ expect.arrayContaining(["sharp", "canvas", "my-pkg"])
+ );
+ expect(result.traceInclude).toHaveLength(3);
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it("returns named deps as traceInclude (builtins + user, RegExp excluded)", () => { | |
| const result = resolveTraceDeps(["my-pkg", /my-.*-pkg/], defaults); | |
| expect(result.traceInclude).toContain("sharp"); | |
| expect(result.traceInclude).toContain("canvas"); | |
| expect(result.traceInclude).toContain("my-pkg"); | |
| // RegExp entries cannot be resolved by name and must be excluded | |
| expect(result.traceInclude!.every((d) => typeof d === "string")).toBe(true); | |
| }); | |
| it("returns named deps as traceInclude (builtins + user, RegExp excluded)", () => { | |
| const result = resolveTraceDeps(["my-pkg", /my-.*-pkg/], defaults); | |
| expect(result.traceInclude).toEqual( | |
| expect.arrayContaining(["sharp", "canvas", "my-pkg"]) | |
| ); | |
| expect(result.traceInclude).toHaveLength(3); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@test/unit/trace-deps.test.ts` around lines 43 - 50, The trace deps test is
too loose because it only checks that traceInclude entries are strings, which
would still pass if the RegExp selector were incorrectly stringified. Tighten
the assertion in trace-deps.test.ts around resolveTraceDeps to verify the exact
traceInclude membership and count for the ["my-pkg", /my-.*-pkg/] case, using
the existing defaults fixture and the resolveTraceDeps result to ensure only the
expected builtins plus "my-pkg" are present.
commit: |
|
@pi0 It might be related to the pkg.pr preview 🤔, but I am getting the following error when building Nitropage with this preview: |
|
Very strange @katywings bundled @vercel/nft is not even ESM, it is CJS, it seems something is trying to bundle nitro > nf3 > vercel/nft itself inside output!! Do you have a branch i can test? |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
docs/2.deploy/20.providers/zerops.md (1)
87-97: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winStale access-token/login instructions left alongside new push command.
Lines 87-91 (get access token,
zcli login <token>) remain even though the intent of this change is to replace the manual login-then-deploy flow with a direct@zerops/zcli push(Line 95). Ifzcli pushno longer requires a prior manual login, these leftover steps are confusing/redundant; if it still requires login, the flow should say so explicitly.📝 Suggested cleanup
## Build and deploy -Open [Settings > Access Token Management](https://app.zerops.io/settings/token-management) in the Zerops app and generate a new access token. - -Log in using your access token with the following command: - -:pm-x{command="`@zerops/zcli` login <token>"} - Navigate to the root of your app (where `zerops.yaml` is located) and run the following command to trigger the deploy:🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/2.deploy/20.providers/zerops.md` around lines 87 - 97, The Zerops deployment section still includes the old access-token and `@zerops/zcli login <token>` steps even though the updated flow centers on `@zerops/zcli push`. Update the instructions in the Zerops provider doc so the flow is explicit and non-redundant: either remove the manual login/token steps if `push` works directly, or clearly state that `login` is required before `push`. Use the `zcli login` and `zcli push` steps as the reference points when editing the deployment instructions.src/build/virtual/public-assets.ts (1)
96-101: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winSilent fallback when
serveStaticvalue is unrecognized (e.g. stale"deno").
readAssetHandler[...] || "null"silently degrades to a no-op asset reader ifserveStaticdoesn't match a known key — which now includes any leftover"deno"value from configs written before this change (untyped JS configs, extended presets, or dynamic values bypass the type-level removal). This means all public asset reads would silently resolve tonullwith no error or warning, fully breaking static asset serving without any diagnostic. As per coding guidelines,src/**/*.{ts,js}should "Prefer explicit errors over silent failures; use warnings for recoverable situations; throw for invalid states" and "Include actionable context in error messages."🛡️ Proposed fix to warn on unrecognized `serveStatic` values
// prettier-ignore type _serveStaticAsKey = Exclude<typeof nitro.options.serveStatic, boolean> | "true" | "false"; // prettier-ignore - const handlerName = readAssetHandler[nitro.options.serveStatic as _serveStaticAsKey] || "null"; + const resolvedHandlerName = readAssetHandler[nitro.options.serveStatic as _serveStaticAsKey]; + if (!resolvedHandlerName && nitro.options.serveStatic !== false) { + nitro.logger.warn( + `Unknown \`serveStatic\` value "${nitro.options.serveStatic}", falling back to no static asset serving.` + ); + } + const handlerName = resolvedHandlerName || "null";🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/build/virtual/public-assets.ts` around lines 96 - 101, In the virtual public-assets builder, `readAssetHandler[nitro.options.serveStatic as _serveStaticAsKey] || "null"` silently hides invalid `serveStatic` values and falls back to a no-op reader; update `handlerName` resolution in `src/build/virtual/public-assets.ts` to explicitly detect unrecognized values (including stale `"deno"` or other dynamic inputs), then emit a clear warning or throw with actionable context instead of defaulting to `"null"`. Keep the existing `readAssetHandler` mapping and `readAssetImport` flow, but guard the lookup so `nitro.options.serveStatic` is validated before constructing the virtual import string.Source: Coding guidelines
🧹 Nitpick comments (1)
package.json (1)
90-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAvoid
"latest"for a pinned-style manifest.
@cloudflare/workers-typesis set to"latest"while every other dependency uses a caret-pinned range. This makes installs non-reproducible and can silently pull in breaking type changes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@package.json` at line 90, The dependency declaration for `@cloudflare/workers-types` is using a moving "latest" tag instead of a pinned semver range, which makes installs non-reproducible. Update the package.json dependency entry to match the other caret-pinned versions so the manifest stays stable and avoids unexpected type changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@docs/2.deploy/20.providers/zerops.md`:
- Around line 87-97: The Zerops deployment section still includes the old
access-token and `@zerops/zcli login <token>` steps even though the updated flow
centers on `@zerops/zcli push`. Update the instructions in the Zerops provider
doc so the flow is explicit and non-redundant: either remove the manual
login/token steps if `push` works directly, or clearly state that `login` is
required before `push`. Use the `zcli login` and `zcli push` steps as the
reference points when editing the deployment instructions.
In `@src/build/virtual/public-assets.ts`:
- Around line 96-101: In the virtual public-assets builder,
`readAssetHandler[nitro.options.serveStatic as _serveStaticAsKey] || "null"`
silently hides invalid `serveStatic` values and falls back to a no-op reader;
update `handlerName` resolution in `src/build/virtual/public-assets.ts` to
explicitly detect unrecognized values (including stale `"deno"` or other dynamic
inputs), then emit a clear warning or throw with actionable context instead of
defaulting to `"null"`. Keep the existing `readAssetHandler` mapping and
`readAssetImport` flow, but guard the lookup so `nitro.options.serveStatic` is
validated before constructing the virtual import string.
---
Nitpick comments:
In `@package.json`:
- Line 90: The dependency declaration for `@cloudflare/workers-types` is using a
moving "latest" tag instead of a pinned semver range, which makes installs
non-reproducible. Update the package.json dependency entry to match the other
caret-pinned versions so the manifest stays stable and avoids unexpected type
changes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e52d7b0c-b87f-4c43-b200-68a3cb25f6cb
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (14)
docs/1.docs/99.migration.mddocs/2.deploy/20.providers/zerops.mddocs/3.config/0.index.mdpackage.jsonsrc/build/plugins/route-meta.tssrc/build/virtual/public-assets.tssrc/build/vite/env.tssrc/presets/deno/preset.tssrc/presets/vercel/utils.tssrc/types/config.tstest/unit/public-assets.test.tstest/vite/openapi-fixture/api/meta/test.tstest/vite/openapi-fixture/vite.config.tstest/vite/openapi.test.ts
✅ Files skipped from review due to trivial changes (3)
- test/vite/openapi-fixture/vite.config.ts
- src/build/plugins/route-meta.ts
- docs/1.docs/99.migration.md
Closes #4372
Problem
Under pnpm, transitive dependencies are not hoisted to the top-level
node_modules— a native, non-bundleable dependency (e.g.sharp/bcrypt) that is only an indirect dependency lives exclusively undernode_modules/.pnpm/.... nft can't statically detect such packages when they're loaded dynamically (native bindings), so they're dropped from the trace and the build fails / ships incomplete output. The known workaround was pnpm'spublicHoistPattern.Fix
resolveTraceDeps()now also returnstraceInclude— the resolved named deps (builtins + usertraceDeps, negations removed, RegExp entries excluded since they can't be resolved by name). ThebuildEndhandler forwards them to nf3'straceNodeModulesvia its newtraceIncludeoption.nf3 resolves each name from
rootDirand from the roots of traced packages that declare it as a dependency — so a pnpm nested dep resolves from its dependent's real.pnpmlocation and gets force-traced (native binaries included).Depends on
traceIncludeoption totraceNodeModules(resolved dependency-driven, so forwarding the full builtin allowlist stays cheap).Draft until that nf3 change is released and the
nf3dependency here is bumped to a version that includes it (otherwise CI typecheck fails against the published types).Tests
test/unit/trace-deps.test.ts— added coverage for thetraceIncludeoutput (builtins + user, RegExp excluded, negation handling). All unit tests pass.