willow-cli
Version:
CLI for installing Willow Design System components
172 lines (171 loc) • 8.72 kB
JavaScript
import chalk from 'chalk';
import { readFileContent, writeFileContent } from './fileSystem.js';
import { glob } from 'glob';
/**
* Fix incorrect Willow component imports in TypeScript/JavaScript files
*/
export async function fixWillowImports(baseDir = process.cwd()) {
console.log(chalk.blue('🔧 Checking for incorrect Willow imports...'));
const files = await glob('**/*.{ts,tsx,js,jsx}', {
cwd: baseDir,
ignore: ['node_modules/**', 'dist/**', 'build/**', '.next/**']
});
let fixedFiles = 0;
for (const file of files) {
try {
const content = await readFileContent(file);
const originalContent = content;
// Fix @willow/components imports
let updatedContent = content.replace(/import\s*\{([^}]+)\}\s*from\s*["']@willow\/components["'];?/g, (match, imports) => {
console.log(chalk.yellow(` ⚠️ Found incorrect import in ${file}`));
console.log(chalk.gray(` ${match.trim()}`));
// Parse the imports and create individual import statements
const importList = imports
.split(',')
.map((imp) => imp.trim())
.filter((imp) => imp.length > 0);
const individualImports = importList.map((componentName) => {
const kebabCase = componentNameToPath(componentName);
return `import { ${componentName} } from "@/components/ui/${kebabCase}";`;
}).join('\n');
console.log(chalk.green(` ✅ Fixed to individual imports:`));
individualImports.split('\n').forEach((line) => {
console.log(chalk.gray(` ${line}`));
});
return individualImports;
});
// Fix barrel imports from @/components/ui to individual imports
updatedContent = updatedContent.replace(/import\s*\{([^}]+)\}\s*from\s*["']@\/components\/ui["'];?/g, (match, imports) => {
console.log(chalk.yellow(` ⚠️ Found barrel import in ${file}: ${match}`));
// Parse the imports and create individual import statements
const importList = imports
.split(',')
.map((imp) => imp.trim())
.filter((imp) => imp.length > 0);
const individualImports = importList.map((componentName) => {
const kebabCase = componentNameToPath(componentName);
return `import { ${componentName} } from "@/components/ui/${kebabCase}";`;
}).join('\n');
console.log(chalk.green(` ✅ Fixed to individual imports:`));
individualImports.split('\n').forEach((line) => {
console.log(chalk.gray(` ${line}`));
});
return individualImports;
});
// Fix any other common incorrect patterns
updatedContent = updatedContent.replace(/from\s*["']@willow\/([^"']+)["'];?/g, (match, path) => {
console.log(chalk.yellow(` ⚠️ Found incorrect @willow/ import in ${file}: ${match}`));
const replacement = `from "@/components/ui/${path}";`;
console.log(chalk.green(` ✅ Fixed to: ${replacement}`));
return replacement;
});
// Fix incorrect path patterns
updatedContent = updatedContent.replace(/from\s*["']@\/src\/components\/ui\/([A-Z][a-zA-Z]*)["'];?/g, (match, componentName) => {
const kebabCase = componentName.toLowerCase();
console.log(chalk.yellow(` ⚠️ Found incorrect path in ${file}: ${match}`));
const replacement = `from "@/components/ui/${kebabCase}";`;
console.log(chalk.green(` ✅ Fixed to: ${replacement}`));
return replacement;
});
// Fix @/components/ui/[CapitalCase] -> @/components/ui/[lowercase]
updatedContent = updatedContent.replace(/from\s*["']@\/components\/ui\/([A-Z][a-zA-Z]*)["'];?/g, (match, componentName) => {
const kebabCase = componentName.toLowerCase();
if (componentName !== kebabCase) {
console.log(chalk.yellow(` ⚠️ Found incorrect casing in ${file}: ${match}`));
const replacement = `from "@/components/ui/${kebabCase}";`;
console.log(chalk.green(` ✅ Fixed to: ${replacement}`));
return replacement;
}
return match;
});
// Fix relative imports with incorrect casing (e.g., ./Button -> ./button)
updatedContent = updatedContent.replace(/from\s*["']\.\/([A-Z][a-zA-Z]+)["'];?/g, (match, componentName) => {
const kebabCase = componentName.toLowerCase();
console.log(chalk.yellow(` ⚠️ Found incorrect casing in ${file}: ${match}`));
const replacement = `from "./${kebabCase}";`;
console.log(chalk.green(` ✅ Fixed to: ${replacement}`));
return replacement;
});
// Fix cn utility imports from ./utils to @/lib/utils
updatedContent = updatedContent.replace(/from\s*["']\.\/utils["'];?/g, (match) => {
console.log(chalk.yellow(` ⚠️ Found incorrect utils import in ${file}: ${match}`));
const replacement = `from "@/lib/utils";`;
console.log(chalk.green(` ✅ Fixed to: ${replacement}`));
return replacement;
});
// Fix other relative utils imports
updatedContent = updatedContent.replace(/from\s*["']\.\.\/(lib\/)?utils["'];?/g, (match) => {
console.log(chalk.yellow(` ⚠️ Found incorrect utils import in ${file}: ${match}`));
const replacement = `from "@/lib/utils";`;
console.log(chalk.green(` ✅ Fixed to: ${replacement}`));
return replacement;
});
// Fix missing file extensions in online IDEs
updatedContent = updatedContent.replace(/from\s*["'](@\/[^"']+)["'](?!\.)/g, (match, importPath) => {
if (!importPath.includes('.') && !importPath.endsWith('/')) {
console.log(chalk.yellow(` ⚠️ Adding .js extension for online IDE in ${file}: ${match}`));
const replacement = `from "${importPath}.js"`;
console.log(chalk.green(` ✅ Fixed to: ${replacement}`));
return replacement;
}
return match;
});
if (updatedContent !== originalContent) {
await writeFileContent(file, updatedContent);
fixedFiles++;
console.log(chalk.green(` ✅ Fixed imports in ${file}`));
}
}
catch (error) {
console.log(chalk.yellow(` ⚠️ Could not process ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`));
}
}
if (fixedFiles > 0) {
console.log(chalk.green(`✅ Fixed incorrect imports in ${fixedFiles} file(s)`));
}
else {
console.log(chalk.green('✅ No incorrect imports found'));
}
}
/**
* Convert component name to kebab-case for file path
*/
function componentNameToPath(componentName) {
// Handle common component names
const mappings = {
'Button': 'button',
'Card': 'card',
'CardContent': 'card',
'CardHeader': 'card',
'CardTitle': 'card',
'CardFooter': 'card',
'Badge': 'badge',
'Input': 'input',
'Label': 'label',
'Tabs': 'tabs',
'TabsList': 'tabs',
'TabsTrigger': 'tabs',
'TabsContent': 'tabs',
'Dialog': 'dialog',
'DialogContent': 'dialog',
'DialogHeader': 'dialog',
'DialogTitle': 'dialog',
'DialogTrigger': 'dialog',
'Alert': 'alert',
'AlertDialog': 'alert-dialog',
'Avatar': 'avatar',
'AvatarImage': 'avatar',
'AvatarFallback': 'avatar',
'Checkbox': 'checkbox',
'RadioGroup': 'radio-group',
'Select': 'select',
'Switch': 'switch',
'Textarea': 'textarea',
'Toast': 'toast',
'Tooltip': 'tooltip',
'TooltipContent': 'tooltip',
'TooltipProvider': 'tooltip',
'TooltipTrigger': 'tooltip'
};
return mappings[componentName] || componentName.toLowerCase().replace(/([A-Z])/g, '-$1').replace(/^-/, '');
}