Skip to content

Conversation

@JRhodes95
Copy link

! Note - the majority of the code here was generated by Claude Code, but I have manually verified the fix locally and agree with its approach.

Summary

Fixes pnpm compatibility by replacing brittle build-time string replacement with robust runtime path detection. This allows convex-test to work seamlessly with pnpm's symlinked node_modules structure.

Problem

The current build process uses replace-in-file to change "./convex/**/*.*s" to "../../../convex/**/*.*s" in the compiled JavaScript. This hardcoded path assumes npm's nested node_modules structure but breaks with pnpm's unique layout:

npm/yarn structure:

project/ ├── convex/ (target directory) └── node_modules/ └── convex-test/ └── dist/index.js (needs "../../../" to reach convex/) 

pnpm structure:

project/ ├── convex/ (target directory) └── node_modules/ ├── convex-test/ (symlink) └── .pnpm/ └── convex-test@version_deps/ └── node_modules/ └── convex-test/ └── dist/index.js (hardcoded "../../../" fails here) 

Pnpm is also stricter on peer dependency resolution. Although we import vitest, the import meta URL and import meta glob APIs are from Vite's core library, which weren't present when installing worked with pnpm. The first commit fixes this.

Solution

Replaced build-time path manipulation with runtime path detection:

  1. Removed brittle dependencies:

    • Eliminated replace-in-file build step and dependency
    • Removed hardcoded path assumptions
  2. Added dynamic project root detection:

    function findProjectRoot(): string { const currentPath = fileURLToPath(import.meta.url); const nodeModulesIndex = currentPath.indexOf("/node_modules/"); return nodeModulesIndex !== -1 ? currentPath.substring(0, nodeModulesIndex) : process.cwd(); }
  3. Implemented runtime module discovery:

    • Recursively scans convex directory using Node.js filesystem APIs
    • Creates dynamic import functions for discovered modules
    • Works with any package manager structure

Key Changes

  • findProjectRoot(): Detects project root by splitting on first /node_modules/
  • findConvexFiles(): Recursively scans convex directory at runtime
  • moduleCache(): Uses runtime detection instead of static import.meta.glob
  • Simplified build: Clean tsc compilation without string replacement

Testing

  • All existing tests pass (109/109)
  • Tested with pnpm, and npm
  • No breaking changes to public API
  • Maintains backward compatibility with user-provided modules parameter

To test this locally, I:

  1. created basic projects using convexTest and PNPM.
  2. I created a test that passed with npm and then failed when I used it with PNPM.
  3. I built and packed up the convex-test from my local branch and used that tarball as the source in my problematic PNPM project, and saw that the tests now pass.

Benefits

  • Universal package manager support - Should now work with npm, yarn, pnpm, and others
  • Robust fallbacks - Falls back to process.cwd() if path detection fails
  • Cleaner build process - No more string replacement

🤖 Generated with Claude Code

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

… with runtime detection - Remove brittle replace-in-file build step that broke with pnpm's symlinked structure - Implement dynamic project root detection using first /node_modules/ split - Add runtime module discovery for convex directory scanning - Maintain backward compatibility with user-provided modules parameter 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Contributor

@ianmacartney ianmacartney left a comment

Choose a reason for hiding this comment

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

thanks for putting this up! I agree the hardcoded lookup is so brittle & confusing for folks. hopefully this can be a big jump forward for pnpm users as well as other custom function location users

function findConvexFiles(
projectRoot: string,
): Record<string, () => Promise<any>> {
const convexDir = join(projectRoot, "convex");
Copy link
Contributor

Choose a reason for hiding this comment

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

ideally, we could also see if there's a convex.json in the project root that has a functions path, which is used to point to custom function locations.

: "If your Convex functions aren't defined in a directory " +
'called "convex" sibling to your node_modules, ' +
"provide the second argument to `convexTest`"),
: "convex-test automatically detected your project root and convex directory. " +
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe we could say where we tried to find them (show the project root path e.g.)

"provide the second argument to `convexTest`"),
: "convex-test automatically detected your project root and convex directory. " +
"If your Convex functions are in a non-standard location, " +
"provide the modules parameter to `convexTest` with your custom `import.meta.glob` pattern."),
Copy link
Contributor

Choose a reason for hiding this comment

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

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 8, 2025

Open in StackBlitz

npm i https://pkg.pr.new/get-convex/convex-test@49 

commit: 4cd377b

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

Labels

None yet

2 participants