Skip to content

Instantly share code, notes, and snippets.

@jmfrancois
Created January 13, 2026 10:05
Show Gist options
  • Select an option

  • Save jmfrancois/7980b376ea2b9cd5c0ff2131b1450c08 to your computer and use it in GitHub Desktop.

Select an option

Save jmfrancois/7980b376ea2b9cd5c0ff2131b1450c08 to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
/**
* # Preview what would be renamed (dry run)
* node rename-jsx-files.mjs packages/components --dry-run
*
* # Actually rename files
* node rename-jsx-files.mjs packages/components
*
* # Verbose mode to see all files processed
* node rename-jsx-files.mjs packages/components --verbose
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
// Patterns that indicate JSX/React code
const JSX_PATTERNS = [
/return\s*\(\s*<[A-Za-z]/m, // return (<Component
/return\s*<[A-Z][A-Za-z0-9]/m, // return <Component
/return\s*<>/m, // return <>
/return\s*<\/>/m, // return </>
/=>\s*\(\s*<[A-Za-z]/m, // => (<div or => (<Component
/=>\s*<[A-Z][A-Za-z0-9]/m, // => <Component
/=>\s*<>/m, // => <>
/createElement\(/m, // React.createElement(
];
// Additional patterns for JSX elements
const JSX_ELEMENT_PATTERNS = [
/<[A-Z][A-Za-z0-9._:-]*[\s>]/, // <Component> or <Component
/<\/[A-Z][A-Za-z0-9._:-]*>/, // </Component>
/<[a-z][a-z0-9]*[\s>]/, // <div> or <span
/<\/[a-z][a-z0-9]*>/, // </div> or </span>
/<>\s*$/m, // <>
/<\/>\s*$/m, // </>
];
function hasJSXContent(content) {
// Check primary JSX patterns (return statements, arrow functions)
for (const pattern of JSX_PATTERNS) {
if (pattern.test(content)) {
return true;
}
}
// Check for JSX elements, but be more careful to avoid false positives
// Only consider it JSX if we find multiple element patterns or imports from react
const hasReactImport = /from\s+['"]react['"]|require\(['"]react['"]\)/.test(content);
if (hasReactImport) {
for (const pattern of JSX_ELEMENT_PATTERNS) {
if (pattern.test(content)) {
return true;
}
}
}
return false;
}
export function renameJSXFiles(dir, options = {}) {
const {
dryRun = false,
verbose = false,
exclude = ['node_modules', '.git', 'dist', 'build', 'lib', 'lib-esm', '.turbo'],
} = options;
const results = {
scanned: 0,
renamed: 0,
errors: 0,
files: [],
};
function processDirectory(currentDir) {
let entries;
try {
entries = fs.readdirSync(currentDir, { withFileTypes: true });
} catch (err) {
console.error(`Error reading directory ${currentDir}:`, err.message);
results.errors++;
return;
}
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
if (entry.isDirectory()) {
// Skip excluded directories
if (exclude.includes(entry.name)) {
if (verbose) {
console.log(`Skipping excluded directory: ${fullPath}`);
}
continue;
}
processDirectory(fullPath);
} else if (entry.isFile() && entry.name.endsWith('.js')) {
results.scanned++;
try {
const content = fs.readFileSync(fullPath, 'utf8');
if (hasJSXContent(content)) {
const newPath = fullPath.replace(/\.js$/, '.jsx');
results.files.push({ from: fullPath, to: newPath });
if (dryRun) {
console.log(`[DRY RUN] Would rename: ${fullPath} -> ${newPath}`);
} else {
fs.renameSync(fullPath, newPath);
console.log(`✓ Renamed: ${fullPath} -> ${newPath}`);
}
results.renamed++;
} else if (verbose) {
console.log(`Skipped (no JSX): ${fullPath}`);
}
} catch (err) {
console.error(`Error processing ${fullPath}:`, err.message);
results.errors++;
}
}
}
}
processDirectory(dir);
return results;
}
// Check if script is being run directly
const isMainModule = process.argv[1] === fileURLToPath(import.meta.url);
// CLI usage
if (isMainModule) {
const args = process.argv.slice(2);
const options = {
dryRun: args.includes('--dry-run') || args.includes('-d'),
verbose: args.includes('--verbose') || args.includes('-v'),
};
const targetDir = args.find(arg => !arg.startsWith('--') && !arg.startsWith('-')) || '.';
if (args.includes('--help') || args.includes('-h')) {
console.log(`
Usage: node rename-jsx-files.mjs [directory] [options]
Rename .js files containing JSX/React code to .jsx
Options:
-d, --dry-run Show what would be renamed without actually renaming
-v, --verbose Show all files being processed
-h, --help Show this help message
Example:
node rename-jsx-files.mjs packages/components --dry-run
node rename-jsx-files.mjs packages/components
node rename-jsx-files.mjs . --verbose
`);
process.exit(0);
}
console.log(`\nScanning directory: ${path.resolve(targetDir)}`);
console.log(`Mode: ${options.dryRun ? 'DRY RUN' : 'LIVE'}\n`);
const results = renameJSXFiles(targetDir, options);
console.log(`\n${'='.repeat(60)}`);
console.log('Summary:');
console.log(` Files scanned: ${results.scanned}`);
console.log(` Files ${options.dryRun ? 'to rename' : 'renamed'}: ${results.renamed}`);
console.log(` Errors: ${results.errors}`);
console.log(`${'='.repeat(60)}\n`);
if (results.errors > 0) {
process.exit(1);
}
}
export { hasJSXContent };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment