Rename Files
Rename All Files and Folders in src
to kebab-case
// rename-files.js
import fs from 'fs'
import { execSync } from 'node:child_process'
import path from 'node:path'
function toKebabCase(str) {
return str
.replace(/([a-z\d])([A-Z])/g, '$1-$2') // camelCase or PascalCase to kebab-case
.replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1-$2') // Handle cases like XMLHTTPRequest
.replace(/[\s_]+/g, '-') // spaces or underscores to dashes
.toLowerCase()
}
function isTrackedByGit(filePath) {
try {
execSync(`git ls-files --error-unmatch "${filePath}"`, { stdio: 'ignore' })
return true
} catch {
return false
}
}
function renameWithGitMv(oldPath, newPath) {
if (!fs.existsSync(oldPath)) {
console.info(`Skipped (source doesn't exist): ${oldPath}`)
return
}
if (oldPath === newPath) {
console.info(`Skipped (already renamed): ${oldPath}`)
return
}
if (isTrackedByGit(oldPath)) {
const tempPath = `${newPath}_temp_${Date.now()}`
execSync(`git mv "${oldPath}" "${tempPath}"`)
execSync(`git mv "${tempPath}" "${newPath}"`)
console.info(`Renamed (git mv): ${oldPath} -> ${newPath}`)
} else {
fs.renameSync(oldPath, newPath)
console.info(`Renamed (fs.renameSync): ${oldPath} -> ${newPath}`)
}
}
function convertSingleIndexFolders(dir) {
const entries = fs.readdirSync(dir)
entries.forEach((entry) => {
const oldPath = path.join(dir, entry)
const stat = fs.statSync(oldPath)
if (stat.isDirectory()) {
const subEntries = fs.readdirSync(oldPath)
if (subEntries.length === 1 && subEntries[0] === 'index.tsx') {
const newFileName = `${toKebabCase(entry)}.tsx`
const newPath = path.join(dir, newFileName)
const indexFilePath = path.join(oldPath, 'index.tsx')
renameWithGitMv(indexFilePath, newPath)
fs.rmdirSync(oldPath)
console.info(`Converted folder to file: ${oldPath} -> ${newPath}`)
return
}
}
if (stat.isDirectory()) {
convertSingleIndexFolders(oldPath) // Recursively handle subdirectories
}
})
}
function renameFilesAndFolders(dir) {
const entries = fs.readdirSync(dir).sort((a, b) => b.length - a.length)
entries.forEach((entry) => {
const oldPath = path.join(dir, entry)
const stat = fs.statSync(oldPath)
const newEntry = toKebabCase(entry)
const newPath = path.join(dir, newEntry)
if (oldPath !== newPath) {
// Skip if already in kebab case
if (entry === newEntry) {
console.info(`Skipped (already kebab case): ${oldPath}`)
return
}
// Rename files or directories using git mv
renameWithGitMv(oldPath, newPath)
if (stat.isDirectory()) {
// Recursively rename contents if it's a directory
renameFilesAndFolders(newPath)
}
console.info(`Renamed: ${oldPath} -> ${newPath}`)
} else if (stat.isDirectory()) {
// Process contents of directories that are already in kebab-case
renameFilesAndFolders(newPath)
}
})
}
convertSingleIndexFolders('./src') // Convert single index.tsx folders first
renameFilesAndFolders('./src') // Replace with your root directory if different
Then run:
node rename-files.js