Skip to content

fix(cli): normalize Windows paths for module identifiers and add test… #148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

kmr-rohit
Copy link

… (#140)

fix(cli): issue #140 - normalize Windows paths for module identifiers and added tests for windows paths.


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

@kmr-rohit
Copy link
Author

@nipunn1313 , can you look into it.

@nipunn1313
Copy link
Collaborator

Been a little busy - will try to get to it with some time. Thanks for the contribution.

let noDrive = posixRelPath.replace(/^[A-Za-z]:\\|^[A-Za-z]:\//, "");
// Convert all backslashes to forward slashes
return noDrive.replace(/\\/g, "/");
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I suspect we're going to want to use standard libraries rather than doing our own custom string crunching like this.

Windows paths are incredibly complex and there are a lot of ways they can be represented - sticking to standard libraries rather than writing custom will be worthwhile.

https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats

Would also appreciate if you could look into where the module path is used and why it's sent up to the server. That may help us understand what the right solution is. In general, having OS-specific paths sent to the server is probably not the right architecture since the same convex backend can be developed with from multiple OSes.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks @nipunn1313 , i will take a look at standard libraries for that, also will check usage & why it is sent up to server. Thanks for the input

@kmr-rohit
Copy link
Author

Hey @nipunn1313, thanks for the feedback! You're absolutely right about using standard libraries and understanding the architecture better.

I dug into how module paths actually work and found some interesting things:

The real issue: Module paths aren't file paths at all - they're logical identifiers like "convex/myFunction.js" that the server uses internally. The CLI is supposed to convert OS file paths into these platform-agnostic identifiers before sending them over.

Looking at the server code in module_path.rs, it's pretty strict about what it accepts:

  • Must be relative paths (rejects anything absolute)
  • Forward slashes only
  • No .. components
  • Has to pass check_valid_path_component() validation

So when someone on Windows has C:/path/to/out/convex/foo.js, we need to extract just the convex/foo.js part as the module identifier.

New approach using standard lib:

export function normalizeModulePath(outputPath: string, baseDir: string = "out"): string {
  // Normalize separators first
  const normalizedOutput = outputPath.replace(/\\/g, '/');
  const normalizedBase = baseDir.replace(/\\/g, '/');
  
  const relativePath = path.posix.relative(normalizedBase, normalizedOutput);
  
  // For absolute paths, find where the base dir appears and extract what comes after
  if (relativePath.startsWith('../')) {
    const outputParts = normalizedOutput.split('/');
    const baseParts = normalizedBase.split('/');
    
    const baseIndex = outputParts.findIndex((part, index) => {
      return baseParts.every((basePart, bIndex) => 
        outputParts[index + bIndex] === basePart
      );
    });
    
    if (baseIndex >= 0) {
      return outputParts.slice(baseIndex + baseParts.length).join('/');
    }
    
    return outputParts[outputParts.length - 1];
  }
  
  return relativePath;
}

This way we're using path.posix.relative() and proper path parsing instead of regex, and it correctly handles the Windows absolute path case by extracting the actual module identifier the server expects.

Tests are all passing now - Windows paths like C:\project\out\convex\foo.js properly become convex/foo.js.

@kmr-rohit
Copy link
Author

@nipunn1313 , Had a chance to look into it ?

@nipunn1313
Copy link
Collaborator

Hi @kmr-rohit - not yet. Lot going on over at Convex - it's in my queue.

@kmr-rohit
Copy link
Author

kmr-rohit commented Jul 18, 2025

Totally understandable, @nipunn1313 — with everything going on at Convex and being early adopters of PlanetScale’s Postgres, it’s all shaping up as it’s meant to be.

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