Skip to content

Instantly share code, notes, and snippets.

@drikusroor
Last active January 14, 2026 12:36
Show Gist options
  • Select an option

  • Save drikusroor/d891d53f953be009a1ffd7a0cebcd7f8 to your computer and use it in GitHub Desktop.

Select an option

Save drikusroor/d891d53f953be009a1ffd7a0cebcd7f8 to your computer and use it in GitHub Desktop.
React / Next.js CamelCase to kebab-case migration audit script
/**
bun run audit-kebab.ts
πŸ” Scanning ./src for CamelCase/PascalCase violations (Files, Imports, Mocks)...
πŸ“‚ **File Naming Violations** (1)
-----------------------------------
❌ stores/UserStore.ts
πŸ”— **Reference Violations** (4)
-----------------------------------
❌ [Import] in hooks/use-auth.ts: "@/stores/UserStore"
❌ [Import] in hooks/use-user.test.tsx: "@/stores/UserStore"
❌ [Import] in hooks/use-user.tsx: "@/stores/UserStore"
❌ [Import] in stores/user-store.test.ts: "./UserStore"
Found 5 issues.
or
❯ bun run audit-kebab.ts
πŸ” Scanning ./src for CamelCase/PascalCase violations (Files, Imports, Mocks)...
βœ… All clear! No CamelCase detected.
Made with Google Gemini.
**/
import { Glob } from 'bun';
import { resolve } from 'path';
// --- Configuration ---
const SRC_DIR = './src';
const EXTENSIONS = ['ts', 'tsx', 'js', 'jsx'];
// --- Logic ---
console.log(
`πŸ” Scanning ${SRC_DIR} for CamelCase/PascalCase violations (Files, Imports, Mocks)...\n`,
);
const glob = new Glob(`**/*.{${EXTENSIONS.join(',')}}`);
const violations = {
fileNames: [] as string[],
references: [] as { file: string; type: string; path: string }[],
};
// Regex explanation:
// (?: ... ) -> Non-capturing group for the keyword prefixes
// from -> standard 'import x from "y"'
// import\(? -> dynamic 'import("y")'
// require\( -> CommonJS 'require("y")'
// \.mock\( -> Vitest/Jest 'vi.mock("y")'
// \s* -> Allow whitespace
// ['"]([^'"]+)['"] -> Capture the string inside the quotes
const importPattern = /(?:from|import\(?|require\(|\.mock\()\s*['"]([^'"]+)['"]/g;
const hasUpperCase = (str: string) => /[A-Z]/.test(str);
const isLocalImport = (path: string) => path.startsWith('.') || path.startsWith('@/');
for await (const filePath of glob.scan({ cwd: SRC_DIR })) {
const fullPath = resolve(SRC_DIR, filePath);
const fileName = filePath.split('/').pop() || '';
// 1. Check File Name
if (hasUpperCase(fileName)) {
violations.fileNames.push(filePath);
}
// 2. Check Content (Imports & Mocks)
const content = await Bun.file(fullPath).text();
let match;
importPattern.lastIndex = 0;
while ((match = importPattern.exec(content)) !== null) {
const matchedString = match[0]; // The full match (e.g., 'vi.mock("./HistorySearch")')
const importPath = match[1]; // The capture group (e.g., './HistorySearch')
if (isLocalImport(importPath)) {
if (hasUpperCase(importPath)) {
// Determine type for better logging
let type = 'Import';
if (matchedString.includes('.mock')) type = 'Mock';
else if (matchedString.includes('require')) type = 'Require';
violations.references.push({ file: filePath, type, path: importPath });
}
}
}
}
// --- Report ---
if (violations.fileNames.length > 0) {
console.log(`\nπŸ“‚ **File Naming Violations** (${violations.fileNames.length})`);
console.log('-----------------------------------');
violations.fileNames.forEach(f => console.log(` ❌ ${f}`));
}
if (violations.references.length > 0) {
console.log(`\nπŸ”— **Reference Violations** (${violations.references.length})`);
console.log('-----------------------------------');
violations.references.forEach(v => console.log(` ❌ [${v.type}] in ${v.file}: \t"${v.path}"`));
}
if (violations.fileNames.length === 0 && violations.references.length === 0) {
console.log('βœ… All clear! No CamelCase detected.');
} else {
console.log(`\nFound ${violations.fileNames.length + violations.references.length} issues.`);
process.exit(1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment