crapifyme
Version:
Ultra-fast developer productivity CLI tools - remove comments, logs, and more
235 lines (227 loc) • 12.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.svgCommand = void 0;
const commander_1 = require("commander");
const shared_1 = require("../../shared");
const logic_1 = require("./logic");
const types_1 = require("./types");
exports.svgCommand = new commander_1.Command('svg')
.description('Optimize SVG files or direct SVG code using SVGO')
.argument('[target]', 'SVG file, directory, or direct SVG code to optimize (defaults to current directory)')
.option('--preset <preset>', `Optimization preset (${Object.keys(types_1.SVG_PRESETS).join(', ')})`, 'balanced')
.option('--config <path>', 'Path to custom SVGO configuration file')
.option('--plugins <plugins>', 'Comma-separated list of SVGO plugins to enable')
.option('--precision <number>', 'Floating point precision for coordinates', parseFloat)
.option('--keep-ids', 'Preserve ID attributes')
.option('--keep-titles', 'Preserve title and desc elements for accessibility')
.option('--multipass', 'Run optimization multiple times for better results')
.option('--glob <pattern>', 'Glob pattern for files (e.g., "**/*.svg")')
.option('-e, --extensions <ext>', 'File extensions to process (default: svg)', 'svg')
.option('-x, --exclude <patterns>', 'Comma-separated exclusion patterns')
.option('--in-place', 'Overwrite original files (default with confirmation)')
.option('--copy', 'Create optimized copies with .optimized.svg suffix')
.option('--backup', 'Create .original.svg backup before optimizing')
.option('--stdout', 'Output optimized SVG to console (single files only)')
.option('--output-dir <dir>', 'Save optimized files to different directory')
.option('--parallel', 'Process files in parallel (default: true)', true)
.option('--max-concurrency <number>', 'Maximum number of concurrent operations', parseInt, 4)
.option('--watch', 'Watch mode for continuous optimization during development')
.option('--size-info', 'Show detailed size analysis and compression ratios (default: true)')
.option('--no-size-info', 'Hide size analysis')
.option('--report <format>', 'Export report (json, csv)')
.option('--inline-styles', 'Convert style attributes to inline styles')
.option('--remove-viewbox', 'Remove viewBox when not needed')
.option('--sort-attrs', 'Sort attributes alphabetically')
.option('--remove-xmlns', 'Remove xmlns when not needed for standalone SVGs')
.option('--minify-styles', 'Minify CSS within SVG')
.option('--convert-colors', 'Optimize color representations (hex, named, etc.)')
.option('--validate-input', 'Validate SVG structure before optimization (default: true)', true)
.option('--validate-output', 'Validate SVG structure after optimization (default: true)', true)
.option('--skip-validation', 'Skip all validation checks')
.addHelpText('after', `
Examples:
$ crapifyme svg # Optimize all SVGs in current directory
$ crapifyme svg logo.svg # Optimize single SVG file
$ crapifyme svg assets/ # Optimize all SVGs in directory
$ crapifyme svg '<svg>...</svg>' # Optimize SVG code directly (outputs to console)
$ crapifyme svg --preset=aggressive '<svg>...</svg>' # Direct SVG optimization with preset
$ crapifyme svg --glob "**/*.svg" --preset=aggressive # Aggressive optimization with glob
$ crapifyme svg icon.svg --stdout # Output to console
$ crapifyme svg --copy --preset=minimal assets/ # Create optimized copies with minimal preset
$ crapifyme svg --backup --multipass icons/ # Create backups and run multiple passes
$ crapifyme svg --output-dir=optimized/ src/ # Save to different directory
$ crapifyme svg --keep-ids --keep-titles logo.svg # Preserve IDs and accessibility elements
$ crapifyme svg --plugins="cleanupAttrs,removeComments" logo.svg # Custom plugin selection
$ crapifyme svg --config=svgo.config.js assets/ # Use custom configuration file
$ crapifyme svg --watch --preset=balanced src/ # Watch mode with balanced optimization
$ crapifyme svg --report=json --size-info assets/ # Generate detailed JSON report
$ crapifyme svg --parallel --max-concurrency=8 large-dir/ # High-performance batch processing
Optimization Presets:
minimal Light optimization, preserves most attributes and structure
balanced Balanced optimization with good compression while maintaining usability (default)
aggressive Maximum compression with potential loss of some functionality
Output Modes:
--in-place Overwrite original files (default, prompts for confirmation)
--copy Create .optimized.svg copies alongside originals
--backup Create .original.svg backups before optimization
--stdout Output to console (single files only)
--output-dir Save optimized files to specified directory
Safety Features:
--dry-run Preview changes without modifying files (global option)
--force Bypass version control requirement and confirmations (global option)
--backup Automatic backup creation before optimization
--validate Input/output validation to ensure SVG integrity
Performance Options:
--parallel Process multiple files simultaneously (default: enabled)
--max-concurrency Control number of concurrent operations (default: 4)
--multipass Run optimization multiple times for better compression
Advanced Configuration:
--precision Floating point precision for coordinates (default: 2)
--inline-styles Convert style attributes to inline styles
--remove-viewbox Remove viewBox when not needed
--sort-attrs Sort attributes alphabetically for consistency
--remove-xmlns Remove xmlns declarations for standalone SVGs
--minify-styles Minify CSS content within SVGs
--convert-colors Optimize color representations (hex, named colors, etc.)
Integration Features:
--watch Continuous optimization during development
--report Export detailed reports (json, csv formats)
--config Load custom SVGO configuration files
--glob Advanced file pattern matching
Typical Size Reductions:
Minimal preset: 10-30% file size reduction
Balanced preset: 30-60% file size reduction
Aggressive preset: 50-80% file size reduction
Uses SVGO v3+
Visit https://crapify.me for documentation and examples.
`)
.action(async (target, options, command) => {
await handleSvgOptimization(target, options, command);
});
async function handleSvgOptimization(target, options, command) {
const globalOptions = command.parent?.opts() || {};
const logger = new shared_1.Logger(globalOptions.verbose, globalOptions.quiet, globalOptions.json);
try {
const mergedOptions = {
...options,
dryRun: globalOptions.dryRun || options.dryRun,
force: globalOptions.force || options.force,
verbose: globalOptions.verbose || options.verbose,
quiet: globalOptions.quiet || options.quiet,
json: globalOptions.json || options.json
};
if (options.skipValidation) {
mergedOptions.validateInput = false;
mergedOptions.validateOutput = false;
}
if (mergedOptions.preset && !types_1.SVG_PRESETS[mergedOptions.preset]) {
throw new Error(`Invalid preset "${mergedOptions.preset}". Available presets: ${Object.keys(types_1.SVG_PRESETS).join(', ')}`);
}
if (mergedOptions.plugins && typeof mergedOptions.plugins === 'string') {
mergedOptions.plugins = mergedOptions.plugins
.split(',')
.map((p) => p.trim());
}
if (mergedOptions.exclude && typeof mergedOptions.exclude === 'string') {
mergedOptions.exclude = mergedOptions.exclude
.split(',')
.map((p) => p.trim());
}
const processor = new logic_1.SvgProcessor(logger);
const isSvgCode = target && (target.trim().startsWith('<svg') || target.trim().startsWith('<?xml'));
if (isSvgCode) {
if (mergedOptions.verbose) {
logger.info(`Processing direct SVG code`);
logger.info(`Preset: ${mergedOptions.preset || 'balanced'}`);
}
mergedOptions.stdout = true;
mergedOptions.force = true;
const result = await processor.processSvgCode(target, mergedOptions);
if (mergedOptions.json) {
logger.json(result);
}
else {
console.log(result.optimizedContent);
logger.success(``);
if (!mergedOptions.quiet && mergedOptions.sizeInfo !== false) {
const compressionPercent = ((result.bytesSaved / result.originalSize) * 100).toFixed(1);
console.error(`Original size: ${formatBytes(result.originalSize)}`);
console.error(`Optimized size: ${formatBytes(result.optimizedSize)}`);
console.error(`Bytes saved: ${formatBytes(result.bytesSaved)} (${compressionPercent}%)`);
}
}
process.exit(shared_1.ExitCode.Success);
}
else {
const targetPath = target || process.cwd();
if (mergedOptions.verbose) {
logger.info(`Starting SVG optimization`);
logger.info(`Target: ${targetPath}`);
logger.info(`Preset: ${mergedOptions.preset || 'balanced'}`);
if (mergedOptions.dryRun) {
logger.info('Dry run mode - no files will be modified');
}
}
const result = await processor.processSvgFiles(targetPath, mergedOptions);
if (mergedOptions.json) {
logger.json(result);
}
else {
await displayResults(result, mergedOptions, logger);
}
const exitCode = result.stats.errors.length > 0
? shared_1.ExitCode.Error
: result.failedFiles.length > 0
? shared_1.ExitCode.IssuesFound
: shared_1.ExitCode.Success;
process.exit(exitCode);
}
}
catch (error) {
logger.error('SVG optimization failed', error);
process.exit(shared_1.ExitCode.Error);
}
}
async function displayResults(result, options, logger) {
const { stats } = result;
if (!options.quiet) {
logger.success(`Processed ${stats.filesProcessed} SVG file${stats.filesProcessed === 1 ? '' : 's'}`);
if (stats.filesSkipped > 0) {
logger.info(`Skipped ${stats.filesSkipped} file${stats.filesSkipped === 1 ? '' : 's'}`);
}
if (options.sizeInfo !== false) {
const compressionPercent = ((stats.bytesSaved / stats.bytesOriginal) * 100).toFixed(1);
console.log(` ┣ Original size: ${formatBytes(stats.bytesOriginal)}`);
console.log(` ┣ Optimized size: ${formatBytes(stats.bytesOptimized)}`);
console.log(` ┣ Bytes saved: ${formatBytes(stats.bytesSaved)}`);
console.log(` ┣ Compression: ${compressionPercent}%`);
console.log(` ┣ Avg ratio: ${stats.avgCompressionRatio.toFixed(2)}:1`);
console.log(` ┗ Processing time: ${stats.processingTime.toFixed(2)}ms`);
console.log('');
}
if (stats.errors.length > 0) {
logger.warn(`${stats.errors.length} error${stats.errors.length === 1 ? '' : 's'} encountered:`);
stats.errors.forEach((error) => {
console.log(` ┣ ${error.file}: ${error.error}`);
});
}
if (stats.warnings.length > 0 && options.verbose) {
logger.info(`${stats.warnings.length} warning${stats.warnings.length === 1 ? '' : 's'}:`);
stats.warnings.forEach((warning) => {
console.log(` ┣ ${warning.file}: ${warning.warning}`);
});
}
if (stats.filesProcessed > 0) {
(0, shared_1.showComplete)();
}
}
}
function formatBytes(bytes) {
if (bytes === 0)
return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
}
//# sourceMappingURL=index.js.map