Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions fix-imports-script.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#!/usr/bin/env node

/**
* Post-build script to fix ESM imports by adding .js extensions
*
* This script walks through the dist directory and adds .js extensions
* to all relative imports to ensure compatibility with strict Node.js ESM.
* It also handles directory imports by checking if index.js exists.
*
* Usage: node fix-imports-script.mjs [dist-directory]
*/

import { readdir, readFile, writeFile, access } from 'fs/promises';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

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)


console.log(`🔧 Fixing ESM imports in: ${distDir}\n`);

let filesFixed = 0;
let importsFixed = 0;

/**
* Recursively process all .js files in a directory
*/
async function fixImportsInDirectory(dir) {
try {
const files = await readdir(dir, { withFileTypes: true });

for (const file of files) {
const fullPath = join(dir, file.name);

if (file.isDirectory()) {
await fixImportsInDirectory(fullPath);
} else if (file.name.endsWith('.js')) {
await fixImportsInFile(fullPath);
}
}
} catch (error) {
console.error(`❌ Error processing directory ${dir}:`, error.message);
}
}

/**
* Check if a path exists
*/
async function pathExists(path) {
try {
await access(path);
return true;
} catch {
return false;
}
}

/**
* Resolve the correct import path with extension
*/
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';
}
Comment on lines +63 to +85
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.


/**
* Fix imports in a single JavaScript file
*/
async function fixImportsInFile(filePath) {
try {
let content = await readFile(filePath, 'utf-8');
const originalContent = content;
let fileImportCount = 0;
const fileDir = dirname(filePath);

// Find all import/export statements that need fixing
const patterns = [
// Pattern 1: export * from './path'
/export\s+\*\s+from\s+(['"])(\.\/[^'"]+)\1/g,
// Pattern 2: export { ... } from './path'
/export\s+{[^}]+}\s+from\s+(['"])(\.\/[^'"]+)\1/g,
// Pattern 3: import ... from './path'
/import\s+[^'"]+from\s+(['"])(\.\/[^'"]+)\1/g,
// Pattern 4: import './path'
/import\s+(['"])(\.\/[^'"]+)\1/g
];

let matches = [];
for (const pattern of patterns) {
let match;
while ((match = pattern.exec(content)) !== null) {
const quote = match[1];
const path = match[2];
// Skip if already has extension
if (!path.match(/\.(js|svelte|json)$/)) {
matches.push({
fullMatch: match[0],
quote,
path,
index: match.index
});
}
}
}

// 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++;
}
Comment on lines +127 to +136
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.


// Only write if changes were made
if (content !== originalContent) {
await writeFile(filePath, content, 'utf-8');
filesFixed++;
importsFixed += fileImportCount;
console.log(`✅ Fixed ${fileImportCount} imports in: ${filePath}`);
}
} catch (error) {
console.error(`❌ Error processing file ${filePath}:`, error.message);
}
}

// Run the script
(async () => {
try {
console.log('Starting import fixes...\n');
await fixImportsInDirectory(distDir);
console.log(`\n🎉 Complete! Fixed ${importsFixed} imports across ${filesFixed} files.`);
} catch (error) {
console.error('❌ Script failed:', error);
process.exit(1);
}
})();