UNPKG

fig-folder

Version:

A CLI tool to organize configuration files into a fig/ folder with safe mode option

280 lines (249 loc) 9.8 kB
import fs from 'fs/promises'; import path from 'path'; import chalk from 'chalk'; // Configuration file categories const FILE_CATEGORIES = { // Linting 'eslint.config.mjs': 'Linting', 'eslint.config.js': 'Linting', '.eslintrc': 'Linting', '.eslintrc.js': 'Linting', '.eslintrc.json': 'Linting', '.eslintrc.yaml': 'Linting', '.eslintrc.yml': 'Linting', // Formatting 'prettier.config.mjs': 'Formatting', 'prettier.config.js': 'Formatting', '.prettierrc': 'Formatting', '.prettierrc.js': 'Formatting', '.prettierrc.json': 'Formatting', '.prettierrc.yaml': 'Formatting', '.prettierrc.yml': 'Formatting', // TypeScript 'tsconfig.json': 'TypeScript', 'tsconfig.base.json': 'TypeScript', // Build Tools 'next.config.mjs': 'Build Tools', 'next.config.js': 'Build Tools', 'vite.config.ts': 'Build Tools', 'vite.config.js': 'Build Tools', 'webpack.config.js': 'Build Tools', 'rollup.config.js': 'Build Tools', 'parcel.config.js': 'Build Tools', 'tailwind.config.js': 'Build Tools', 'tailwind.config.ts': 'Build Tools', 'postcss.config.js': 'Build Tools', 'postcss.config.mjs': 'Build Tools', // Environment '.env': 'Environment', '.env.local': 'Environment', '.env.development': 'Environment', '.env.production': 'Environment', '.env.test': 'Environment', // Package Managers 'package-lock.json': 'Package Managers', 'yarn.lock': 'Package Managers', 'pnpm-lock.yaml': 'Package Managers', 'bun.lockb': 'Package Managers', // Git '.gitignore': 'Git', '.gitattributes': 'Git', // Editor '.editorconfig': 'Editor', '.vscode/settings.json': 'Editor', // Testing 'jest.config.js': 'Testing', 'jest.config.ts': 'Testing', 'vitest.config.ts': 'Testing', 'vitest.config.js': 'Testing', 'cypress.config.js': 'Testing', 'cypress.config.ts': 'Testing', // Documentation 'README.md': 'Documentation', 'CHANGELOG.md': 'Documentation', 'LICENSE': 'Documentation', 'CONTRIBUTING.md': 'Documentation' }; // Files that are commonly imported and should NOT be moved (only symlinked) const COMMONLY_IMPORTED_FILES = [ 'tailwind.config.js', 'tailwind.config.ts', 'postcss.config.js', 'postcss.config.mjs', 'next.config.js', 'next.config.mjs', 'vite.config.js', 'vite.config.ts', 'webpack.config.js', 'rollup.config.js', 'parcel.config.js', 'jest.config.js', 'jest.config.ts', 'vitest.config.js', 'vitest.config.ts', 'cypress.config.js', 'cypress.config.ts', 'prettier.config.js', 'prettier.config.mjs', 'eslint.config.js', 'eslint.config.mjs', 'tsconfig.json', 'tsconfig.base.json' ]; // Files to organize const CONFIG_FILES = Object.keys(FILE_CATEGORIES); export async function organize(safe = false) { // ASCII Fig Art console.log(chalk.hex('#8B5CF6')(` ███ ██ ███████ ██ ███ █████ █████ ██ ██ ██████ ██ ██ ███ ████ ███ ██ ███ ██ ███ ███ ███ ███ ███ ██ ███ █████ ███ ███ ██ █ ██ ███ ███ ██ ██ █ ██ ███ ███ █ █ ██ ███ ███ ██ ███ ██ ██ ██ ██ ██ ███ ██ ███ ██ ██ ███ ██ ███ ███ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ █ ██ ██ ██ ██ ██ ██ ███ ███ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ███ ██ ██ ████ ████ ████ ██ ██ ██ ███ ████████ ████████ ███ ███ ██ ██████ ████ ████████ █████████████ ██████ ███████████ ████████████ `)); const figDir = 'fig'; // Create fig/ directory if it doesn't exist try { await fs.mkdir(figDir, { recursive: true }); console.log(chalk.white(`🍑 Created ${figDir}/ directory`)); } catch (error) { if (error.code !== 'EEXIST') { throw error; } } const organizedFiles = []; const categorizedFiles = { 'Linting': [], 'Formatting': [], 'TypeScript': [], 'Build Tools': [], 'Environment': [], 'Package Managers': [], 'Git': [], 'Editor': [], 'Testing': [], 'Documentation': [] }; // Process each config file if it exists for (const file of CONFIG_FILES) { try { const sourcePath = file; const destPath = path.join(figDir, file); // Check if file exists await fs.access(sourcePath); // Create directory structure if needed (for nested files like .vscode/settings.json) const destDir = path.dirname(destPath); if (destDir !== figDir) { await fs.mkdir(destDir, { recursive: true }); } if (safe) { // Safe mode - never move or symlink, just copy try { await fs.copyFile(sourcePath, destPath); console.log(chalk.blue(`📋 Copied ${file}${destPath}`)); } catch (error) { if (error.code === 'EEXIST') { // File already exists in fig/, skip console.log(chalk.gray(`⚠️ File already exists in fig/: ${file}`)); } else { throw error; } } } else if (COMMONLY_IMPORTED_FILES.includes(file)) { // Create symlink for commonly imported files to avoid breaking imports try { await fs.symlink(path.resolve(sourcePath), destPath); console.log(chalk.yellow(`🔗 Symlinked ${file}${destPath}`)); } catch (error) { if (error.code === 'EEXIST') { // Symlink already exists, skip console.log(chalk.gray(`⚠️ Symlink already exists: ${destPath}`)); } else { throw error; } } } else { // Move safe-to-move files try { await fs.rename(sourcePath, destPath); console.log(chalk.white(`📄 Moved ${file}${destPath}`)); } catch (error) { if (error.code === 'EEXIST') { // File already exists in fig/, skip console.log(chalk.gray(`⚠️ File already exists in fig/: ${file}`)); } else { throw error; } } } // Track for README generation organizedFiles.push(file); const category = FILE_CATEGORIES[file]; categorizedFiles[category].push(file); } catch (error) { if (error.code === 'ENOENT') { // File doesn't exist, skip it continue; } else { console.error(chalk.red(`❌ Error processing ${file}: ${error.message}`)); } } } // Generate README.txt await generateReadme(figDir, categorizedFiles, safe); console.log(chalk.white(`📝 Generated ${figDir}/README.txt`)); if (organizedFiles.length === 0) { console.log(chalk.hex('#7C3AED')('🍑 No configuration files found to organize.')); } else { console.log(chalk.hex('#7C3AED')(`✅ Enjoy your Figs! ${organizedFiles.length} configuration file(s).`)); if (safe) { console.log(chalk.gray('💡 Safe mode: Original files remain untouched.')); } else { console.log(chalk.gray('💡 Files that might be imported are symlinked to avoid breaking imports.')); } } } async function generateReadme(figDir, categorizedFiles, safe = false) { let content = '🍑 Your Figs\n\n'; content += 'This directory contains your project\'s configuration files.\n'; if (safe) { content += 'All files are copies - original files remain in your project root.\n\n'; } else { content += 'Files that are commonly imported are symlinked to avoid breaking imports.\n\n'; } // Add each category with its files for (const [category, files] of Object.entries(categorizedFiles)) { if (files.length > 0) { content += `## ${category}\n`; for (const file of files) { if (safe) { content += `- ${file} (copy)\n`; } else { const isSymlinked = COMMONLY_IMPORTED_FILES.includes(file); content += `- ${file}${isSymlinked ? ' (symlinked)' : ''}\n`; } } content += '\n'; } } // Write the README file const readmePath = path.join(figDir, 'README.txt'); await fs.writeFile(readmePath, content, 'utf8'); }