Skip to content

Fixes #79 Resolved ERR_MODULE_NOT_FOUND during Vitest ESM resolution by adding explicit .js extensions to ESM imports.#106

Open
ankitrraj wants to merge 6 commits intoStabilityNexus:mainfrom
ankitrraj:fix/wallet-component-esm-imports
Open

Fixes #79 Resolved ERR_MODULE_NOT_FOUND during Vitest ESM resolution by adding explicit .js extensions to ESM imports.#106
ankitrraj wants to merge 6 commits intoStabilityNexus:mainfrom
ankitrraj:fix/wallet-component-esm-imports

Conversation

@ankitrraj
Copy link
Copy Markdown

@ankitrraj ankitrraj commented Dec 13, 2025

Fixes #79

Resolved ERR_MODULE_NOT_FOUND during Vitest ESM resolution
by adding explicit .js extensions to ESM imports.

the resolve complete

Summary by CodeRabbit

  • Chores
    • Added a post-build script that fixes relative import paths in distribution files (ensures proper JS module extensions and resolves directory entry points), updates only changed files, and emits start/finish logs with a summary of fixes. Accepts an optional target directory.

✏️ Tip: You can customize this high-level summary in your review settings.

…M import issue

- Created comprehensive documentation (WALLET_COMPONENT_FIX.md) explaining the ESM import issue
- Added fix-imports-script.mjs to automatically add .js extensions to imports
- Script successfully fixes module resolution in strict Node.js ESM environments

This addresses the ERR_MODULE_NOT_FOUND error when using wallet-svelte-component
in Vitest and other strict ESM contexts.

The fix needs to be applied upstream to ergo-basics/wallet-svelte-component.
This documentation provides detailed instructions for creating a PR.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 13, 2025

Walkthrough

Adds a new Node.js post-build script that recursively scans a distribution directory and rewrites relative import/export paths to include explicit .js extensions where missing, updating files only when changes are made and logging progress.

Changes

Cohort / File(s) Summary
Build script
fix-imports-script.mjs
New post-build utility that traverses a target dist directory, matches four relative import/export patterns via regex, resolves missing targets (.js or index.js), appends .js extensions where needed, writes files only on change, and logs counts and errors.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • Pay attention to regex coverage for import/export patterns and edge cases (query/hash, dynamic imports).
  • Verify filesystem/path resolution on Windows vs POSIX and symlink handling.
  • Confirm write-on-change behavior and logging fidelity.

Possibly related issues

Poem

🐰 I hop through dist with careful paws,
Finding imports missing their laws,
I stitch on .js where paths were thin,
So Node can find each file within,
Hooray — modules dance and run! 🎉

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: fixing ERR_MODULE_NOT_FOUND errors by adding .js extensions to ESM imports, directly addressing issue #79.
Linked Issues check ✅ Passed The PR introduces a post-build script that automatically fixes relative imports by appending .js extensions, directly addressing the core requirement of issue #79 to resolve ERR_MODULE_NOT_FOUND during ESM resolution.
Out of Scope Changes check ✅ Passed The PR introduces only fix-imports-script.mjs, a utility script focused solely on fixing ESM import extensions—aligned with the stated objective. No extraneous or unrelated changes are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
fix-imports-script.mjs (1)

56-63: Consider preserving original quote style for consistency.

Patterns 1 and 4 normalize all imports to single quotes via template literals, which changes the original code style. While functional, preserving the original quote style would maintain code consistency.

This is a minor style consideration—if consistency with the original code is preferred, you could apply similar quote detection logic as suggested for Patterns 2 and 3.

Also applies to: 85-92

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e430d88 and 3f749f6.

📒 Files selected for processing (4)
  • NEXT_STEPS.md (1 hunks)
  • SUMMARY.md (1 hunks)
  • WALLET_COMPONENT_FIX.md (1 hunks)
  • fix-imports-script.mjs (1 hunks)
🧰 Additional context used
🪛 LanguageTool
SUMMARY.md

[style] ~115-~115: Consider using a different verb for a more formal wording.
Context: ... # This file ``` ## 🎯 The Issue to Be Fixed Upstream Repository: https://githu...

(FIX_RESOLVE)

🪛 markdownlint-cli2 (0.18.1)
NEXT_STEPS.md

107-107: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


117-117: Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (13)
fix-imports-script.mjs (4)

1-24: LGTM! Clear setup with flexible directory configuration.

The script setup is well-structured with proper imports, configurable dist directory, and progress tracking. The default path targets the typical node_modules location, and the CLI argument allows overriding it for different use cases.


26-45: LGTM! Robust directory traversal with proper error handling.

The recursive directory processing is well-implemented with efficient withFileTypes: true usage and graceful error handling that allows the script to continue if individual directories fail.


94-104: LGTM! Efficient file writing with proper tracking.

The conditional write optimization avoids unnecessary disk I/O, and the progress logging with counters provides good visibility into the script's operations.


106-116: LGTM! Clean script execution with proper error handling.

The IIFE pattern is appropriate for top-level async execution, and the error handling with exit code 1 ensures the script properly signals failures in CI/CD pipelines.

WALLET_COMPONENT_FIX.md (5)

1-32: LGTM! Clear problem statement with concrete examples.

The issue description effectively communicates the problem with specific import paths and error messages, making it easy for developers to understand and diagnose the issue.


33-46: LGTM! Accurate root cause analysis.

The explanation correctly identifies the ESM strict mode requirements and the appropriate location for the fix.


64-99: Documentation script is simpler and avoids the quote-handling bug.

Interestingly, the simplified script in this documentation avoids the quote-handling bug present in the actual fix-imports-script.mjs (Patterns 2 and 3) by using straightforward template string replacements. While this normalizes all quotes to single quotes, it's more robust than the substring-based approach in the actual script.

Note: Once the bug in fix-imports-script.mjs is fixed, both approaches will be valid—the actual script preserves quote style while this simplified version normalizes to single quotes.


116-141: LGTM! Clear testing and PR workflow guidance.

The testing steps and PR creation instructions provide a complete workflow for applying and verifying the fix upstream.


142-181: LGTM! Comprehensive testing guidance and helpful references.

The testing section and documentation references provide developers with both practical commands and deeper understanding of ESM requirements.

NEXT_STEPS.md (2)

1-81: LGTM! Clear actionable steps for upstream fix.

The step-by-step guide provides a complete workflow from forking through building, with appropriate Windows-specific commands for the developer's environment.


141-170: LGTM! Helpful alternative and clear cross-references.

The quick local fix option and references to detailed documentation provide flexibility for different workflows. The git status example helps developers verify their current state.

SUMMARY.md (2)

1-104: LGTM! Comprehensive summary with clear status tracking.

The summary effectively documents what was accomplished and provides multiple authentication solutions for the Git push issue. The mention of "fixed 8 imports across 4 files" validates that the script was successfully tested locally.


105-144: LGTM! Well-organized summary with clear rationale.

The file inventory, issue summary, and explanation of the fix's importance provide excellent context for reviewers and future maintainers.

Comment thread fix-imports-script.mjs Outdated
Comment thread fix-imports-script.mjs Outdated
Comment thread NEXT_STEPS.md Outdated
Comment on lines +107 to +117

1. Go to your fork: `https://github.com/ankitrraj/wallet-svelte-component`
2. Click "Pull Requests" → "New Pull Request"
3. Select:
- **base repository:** `ergo-basics/wallet-svelte-component`
- **base branch:** `main` (or whatever their default is)
- **head repository:** `ankitrraj/wallet-svelte-component`
- **compare branch:** `fix/add-js-extensions-to-esm-imports`
4. Title: **"fix: Add .js extensions to ESM imports for strict Node.js compatibility"**
5. Description: Use the content from `WALLET_COMPONENT_FIX.md` sections

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language identifier to fenced code block.

The code block at line 107 is missing a language identifier, which affects syntax highlighting and accessibility tools.

As per static analysis hints, apply this diff:

-```
+```bash
 git add .
 git commit -m "fix: Add .js extensions to ESM imports for strict Node.js compatibility
 ...

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.18.1)</summary>

107-107: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

---

117-117: Bare URL used

(MD034, no-bare-urls)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

In NEXT_STEPS.md around lines 107 to 117 the fenced code block is missing a
language identifier which prevents proper syntax highlighting; update the
opening fence to include the language (e.g., change "" to "bash") and
ensure the matching closing fence remains present so the block renders correctly
and accessibility/syntax tools recognize it.


</details>

<!-- fingerprinting:phantom:poseidon:puma -->

<!-- This is an auto-generated comment by CodeRabbit -->

- Fixed hardcoded single quotes to detect and use correct quote style (single or double)
- Ensures script works correctly regardless of quote style in source files
- Addresses CodeRabbit critical security issue
Keeping only the fix script as reference for upstream PR
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
fix-imports-script.mjs (3)

65-74: Quote detection logic is fragile but likely sufficient for typical dist files.

The quote detection on line 70 addresses the previous review concern but uses a heuristic that could fail if the matched text contains quotes in other contexts (e.g., export { "default" as foo } from './path'). However, for typical compiled dist files, this should work in practice.

For a more robust solution, consider capturing the quote character directly in the regex:

     // Pattern 2: export { ... } from './path'
     content = content.replace(
-      /export\s+{[^}]+}\s+from\s+['"](\.[^'"]+)(?<!\.js|\.svelte|\.json)['"]/g,
-      (match, path) => {
+      /export\s+{[^}]+}\s+from\s+(['"])(\.[^'"]+)(?<!\.js|\.svelte|\.json)\1/g,
+      (match, quote, path) => {
         fileImportCount++;
-        const quote = match.includes('"') && match.lastIndexOf('"') > match.lastIndexOf("'") ? '"' : "'";
-        const exportPart = match.substring(0, match.lastIndexOf(quote));
-        return `${exportPart}${quote}${path}.js${quote}`;
+        return match.replace(new RegExp(`${quote}${path}${quote}$`), `${quote}${path}.js${quote}`);
       }
     );

This captures the opening quote and uses \1 to ensure the closing quote matches.


76-85: Quote detection logic has the same fragility as Pattern 2.

The quote detection on line 81 uses the same heuristic approach as Pattern 2. While it addresses the previous review concern, it could fail in edge cases. For typical dist files, this should work adequately.

For consistency, apply the same regex-based quote capture suggested for Pattern 2:

     // Pattern 3: import ... from './path'
     content = content.replace(
-      /import\s+.*?from\s+['"](\.[^'"]+)(?<!\.js|\.svelte|\.json)['"]/g,
-      (match, path) => {
+      /import\s+.*?from\s+(['"])(\.[^'"]+)(?<!\.js|\.svelte|\.json)\1/g,
+      (match, quote, path) => {
         fileImportCount++;
-        const quote = match.includes('"') && match.lastIndexOf('"') > match.lastIndexOf("'") ? '"' : "'";
-        const importPart = match.substring(0, match.lastIndexOf(quote));
-        return `${importPart}${quote}${path}.js${quote}`;
+        return match.replace(new RegExp(`${quote}${path}${quote}$`), `${quote}${path}.js${quote}`);
       }
     );

58-58: Overly cautious negative lookbehind for dist context.

The negative lookbehind (?<!\.js|\.svelte|\.json) includes .svelte, but Svelte files shouldn't exist in a compiled dist directory. This is harmless but suggests the regex might have been designed for a broader context than post-build dist files.

Also applies to: 67-67, 78-78, 89-89

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f749f6 and a988636.

📒 Files selected for processing (1)
  • fix-imports-script.mjs (1 hunks)
🔇 Additional comments (4)
fix-imports-script.mjs (4)

29-45: LGTM!

The recursive directory traversal logic is clean and appropriate. Error handling allows the script to continue processing remaining files if one fails, which is correct behavior for a build script.


97-102: LGTM!

The write optimization (only writing when changes occur) is good practice and avoids unnecessary file system operations. The logging provides clear feedback on what was fixed.


108-118: LGTM!

The main execution logic is clean and follows best practices for Node.js scripts, including proper error handling and exit codes.


50-106: Consider whether dynamic imports need handling.

The script handles static imports and re-exports but doesn't process dynamic imports like import('./path'). If wallet-svelte-component's dist files contain dynamic imports without extensions, they would also fail ESM resolution.

Verify whether the package uses dynamic imports by checking the dist directory for patterns like import('...') or import("..."). If found, add this pattern to handle them:

// Pattern 5: import('./path')
content = content.replace(
  /import\s*\(\s*['"](\.[^'"]+)(?<!\.js|\.svelte|\.json)['"]\s*\)/g,
  (match, path) => {
    fileImportCount++;
    return `import('${path}.js')`;
  }
);

Comment thread fix-imports-script.mjs
const __dirname = dirname(fileURLToPath(import.meta.url));

// Get dist directory from command line argument or use default
const distDir = process.argv[2] || join(__dirname, 'node_modules', 'wallet-svelte-component', 'dist');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Architectural concern: Patching node_modules is not sustainable.

The default path targets node_modules/wallet-svelte-component/dist, meaning this script modifies an installed dependency. Changes to node_modules are lost on reinstall, requiring this script to run after every npm install.

Consider these more maintainable alternatives:

  • File an issue/PR with the upstream wallet-svelte-component package to ship with proper ESM imports
  • Use patch-package to create a persistent patch
  • Fork the package and publish a fixed version
  • Use a bundler that handles this automatically (e.g., Vite, esbuild)

Comment thread fix-imports-script.mjs Outdated
Comment thread fix-imports-script.mjs Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
fix-imports-script.mjs (1)

19-23: Defaulting to node_modules/.../dist bakes in a fragile workflow
This still encourages patching installed deps as a default behavior (lost on reinstall / lockfile churn). Consider making the dist dir required (or default to your own package’s dist) and pass the dependency path explicitly in CI when needed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a988636 and ff13137.

📒 Files selected for processing (1)
  • fix-imports-script.mjs (1 hunks)

Comment thread fix-imports-script.mjs
Comment on lines +63 to +85
async function resolveImportPath(importPath, fileDir) {
// Skip if already has extension
if (importPath.match(/\.(js|svelte|json)$/)) {
return importPath;
}

// Remove leading './'
const relativePath = importPath.replace(/^\.\//, '');
const fullPath = join(fileDir, relativePath);

// Try path + .js (file)
if (await pathExists(fullPath + '.js')) {
return importPath + '.js';
}

// Try path + /index.js (directory with index)
if (await pathExists(join(fullPath, 'index.js'))) {
return importPath + '/index.js';
}

// Default to .js if neither exists
return importPath + '.js';
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle ../ imports and don’t corrupt non-JS asset imports
Right now you only match ./... and you only treat .js|.svelte|.json as “already has extension”. That can (a) miss valid ../... relative imports and (b) incorrectly rewrite import './styles.css' into ./styles.css.js.

 async function resolveImportPath(importPath, fileDir) {
-  // Skip if already has extension
-  if (importPath.match(/\.(js|svelte|json)$/)) {
+  // Skip if it already has *any* file extension (e.g. .css, .svg, .wasm, .map, ...)
+  // This script is only meant to fix extensionless ESM specifiers.
+  if (/\.[A-Za-z0-9]+$/.test(importPath)) {
     return importPath;
   }
 
-  // Remove leading './'
-  const relativePath = importPath.replace(/^\.\//, '');
-  const fullPath = join(fileDir, relativePath);
+  // Keep './' and '../' semantics intact; `join` handles both.
+  const fullPath = join(fileDir, importPath);
     const patterns = [
       // Pattern 1: export * from './path'
-      /export\s+\*\s+from\s+(['"])(\.\/[^'"]+)\1/g,
+      /export\s+\*\s+from\s+(['"])(\.\.?(?:\/[^'"]+)+)\1/g,
       // Pattern 2: export { ... } from './path'
-      /export\s+{[^}]+}\s+from\s+(['"])(\.\/[^'"]+)\1/g,
+      /export\s+{[^}]+}\s+from\s+(['"])(\.\.?(?:\/[^'"]+)+)\1/g,
       // Pattern 3: import ... from './path'
-      /import\s+[^'"]+from\s+(['"])(\.\/[^'"]+)\1/g,
+      /import\s+[^'"]+from\s+(['"])(\.\.?(?:\/[^'"]+)+)\1/g,
       // Pattern 4: import './path'
-      /import\s+(['"])(\.\/[^'"]+)\1/g
+      /import\s+(['"])(\.\.?(?:\/[^'"]+)+)\1/g
     ];
-        // Skip if already has extension
-        if (!path.match(/\.(js|svelte|json)$/)) {
+        // Skip if it already has any extension
+        if (!/\.[A-Za-z0-9]+$/.test(path)) {
           matches.push({
             fullMatch: match[0],
             quote,
             path,
             index: match.index
           });
         }

Also applies to: 98-107, 115-117

🤖 Prompt for AI Agents
In fix-imports-script.mjs around lines 63-85 (also update analogous logic at
98-107 and 115-117): the resolver only strips a leading "./" and only treats
.js|.svelte|.json as valid extensions, so it misses "../" relative imports and
corrupts imports with other extensions (e.g. .css). Change the leading-path
handling to accept and preserve both "./" and "../" prefixes (don't always
remove just "./"), detect any existing extension generically (use a regex like
/\.[^\/]+$/ to mean “has an extension”) and return the original importPath
unchanged if it already has an extension, and only attempt to append ".js" or
"/index.js" for paths without any extension after checking the filesystem;
ensure returned paths preserve the original relative prefix (./ or ../) and
apply the same fixes to the other occurrences at the stated line ranges.

Comment thread fix-imports-script.mjs
Comment on lines +127 to +136
// Sort by index in reverse to replace from end to start
matches.sort((a, b) => b.index - a.index);

// Process each match
for (const { fullMatch, quote, path } of matches) {
const newPath = await resolveImportPath(path, fileDir);
const newStatement = fullMatch.replace(path, newPath);
content = content.replace(fullMatch, newStatement);
fileImportCount++;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: content.replace(fullMatch, ...) can miss fixes when statements repeat
Because .replace(string, ...) only replaces the first occurrence, identical fullMatch strings elsewhere in the file may remain unchanged (and fileImportCount becomes misleading). Use the stored index and splice the string (you already sort descending, which makes this safe).

-    for (const { fullMatch, quote, path } of matches) {
+    for (const { fullMatch, quote, path, index } of matches) {
       const newPath = await resolveImportPath(path, fileDir);
       const newStatement = fullMatch.replace(path, newPath);
-      content = content.replace(fullMatch, newStatement);
-      fileImportCount++;
+      if (newStatement !== fullMatch) {
+        content =
+          content.slice(0, index) +
+          newStatement +
+          content.slice(index + fullMatch.length);
+        fileImportCount++;
+      }
     }
🤖 Prompt for AI Agents
In fix-imports-script.mjs around lines 127 to 136, using
content.replace(fullMatch, ...) only replaces the first identical occurrence and
can leave other identical matches unchanged; instead use the stored match.index
(you already sorted descending) to splice the newStatement into content at that
exact index and remove the original fullMatch length, increment fileImportCount
for each successful splice, and avoid using String.replace so repeated identical
statements are reliably updated.

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.

ERR_MODULE_NOT_FOUND during Vitest/ESM resolution due to missing .js extension

3 participants