|
1 | 1 | import type { ImgxOptions, ProcessOptions } from '../src/types' |
2 | 2 | import { Buffer } from 'node:buffer' |
3 | | -import { writeFile } from 'node:fs/promises' |
4 | | -import { join } from 'node:path' |
| 3 | +import { copyFile, stat, writeFile } from 'node:fs/promises' |
| 4 | +import { join, resolve } from 'node:path' |
5 | 5 | import proc from 'node:process' |
6 | 6 | import { CAC } from 'cac' |
7 | 7 | import { version } from '../package.json' |
8 | 8 | import { formatReport, generateReport } from '../src/analyze' |
| 9 | +import { generateAppIcons } from '../src/app-icon' |
9 | 10 | import { process } from '../src/core' |
10 | 11 | import { generateSprite } from '../src/sprite-generator' |
11 | 12 | import { generateThumbHash } from '../src/thumbhash' |
12 | 13 | import { debugLog, formatBytes, getFiles } from '../src/utils' |
13 | 14 |
|
| 15 | +// Helper function to parse file size strings like "5MB" or "500KB" |
| 16 | +function parseFileSize(sizeStr: string): number { |
| 17 | + const units = { |
| 18 | + b: 1, |
| 19 | + kb: 1024, |
| 20 | + mb: 1024 * 1024, |
| 21 | + gb: 1024 * 1024 * 1024, |
| 22 | + } |
| 23 | + |
| 24 | + const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*([a-z]+)$/i) |
| 25 | + if (!match) |
| 26 | + return Number.parseInt(sizeStr, 10) || 0 |
| 27 | + |
| 28 | + const size = Number.parseFloat(match[1]) |
| 29 | + const unit = match[2].toLowerCase() |
| 30 | + |
| 31 | + return size * (units[unit] || 1) |
| 32 | +} |
| 33 | + |
| 34 | +// Helper function to format a report |
| 35 | +function formatReport(report: any): string { |
| 36 | + const { stats, summary } = report |
| 37 | + |
| 38 | + let output = ` |
| 39 | +Image Analysis Report |
| 40 | +===================== |
| 41 | +
|
| 42 | +Summary: |
| 43 | +- Total images: ${summary.totalImages} |
| 44 | +- Total size: ${formatBytes(summary.totalSize)} |
| 45 | +- Average size: ${formatBytes(summary.averageSize)} |
| 46 | +- Potential savings: ${summary.potentialSavings} |
| 47 | +
|
| 48 | +Format breakdown: |
| 49 | +${Object.entries(summary.formatBreakdown) |
| 50 | + .map(([format, count]) => `- ${format}: ${count}`) |
| 51 | + .join('\n')} |
| 52 | +
|
| 53 | +Warnings: |
| 54 | +${summary.warnings.length ? summary.warnings.map(w => `- ${w}`).join('\n') : '- None'} |
| 55 | +
|
| 56 | +Details: |
| 57 | +` |
| 58 | + |
| 59 | + for (const stat of stats) { |
| 60 | + output += ` |
| 61 | +${stat.path} |
| 62 | +Size: ${formatBytes(stat.size)} |
| 63 | +Format: ${stat.format} |
| 64 | +Dimensions: ${stat.width}x${stat.height} |
| 65 | +Optimization potential: ${stat.optimizationPotential} |
| 66 | +${stat.warnings.length ? `Warnings:\n${stat.warnings.map(w => `- ${w}`).join('\n')}` : ''} |
| 67 | +` |
| 68 | + } |
| 69 | + |
| 70 | + return output |
| 71 | +} |
| 72 | + |
14 | 73 | const cli = new CAC('imgx') |
15 | 74 |
|
16 | 75 | cli |
|
432 | 491 | console.log('Shell completion not implemented yet') |
433 | 492 | }) |
434 | 493 |
|
| 494 | +cli |
| 495 | + .command('app-icon <input>', 'Generate app icons for macOS and iOS') |
| 496 | + .alias('icon') |
| 497 | + .option('-o, --output-dir <dir>', 'Output directory for app icons', { default: 'assets/app-icons' }) |
| 498 | + .option('-p, --platform <platform>', 'Target platform (macos, ios, all)', { default: 'all' }) |
| 499 | + .option('-v, --verbose', 'Enable verbose logging') |
| 500 | + .example('imgx app-icon app-icon.png') |
| 501 | + .example('imgx app-icon logo.png -o ./src/assets -p macos') |
| 502 | + .action(async (input: string, options?: { outputDir?: string, platform?: 'macos' | 'ios' | 'all', verbose?: boolean }) => { |
| 503 | + if (!input) { |
| 504 | + cli.outputHelp() |
| 505 | + return |
| 506 | + } |
| 507 | + |
| 508 | + try { |
| 509 | + const results = await generateAppIcons(input, { |
| 510 | + outputDir: options.outputDir, |
| 511 | + platform: options.platform, |
| 512 | + }) |
| 513 | + |
| 514 | + console.log(`Generated app icons for ${results.map(r => r.platform).join(', ')}:`) |
| 515 | + |
| 516 | + for (const result of results) { |
| 517 | + console.log(`\n${result.platform.toUpperCase()}:`) |
| 518 | + console.log(`- Output directory: ${options.outputDir}/AppIcon.appiconset`) |
| 519 | + console.log(`- Generated ${result.sizes.length} icon sizes`) |
| 520 | + |
| 521 | + if (options.verbose) { |
| 522 | + for (const size of result.sizes) { |
| 523 | + console.log(` - ${size.filename} (${size.size}x${size.size}px)`) |
| 524 | + } |
| 525 | + } |
| 526 | + } |
| 527 | + } |
| 528 | + catch (error) { |
| 529 | + console.error(`Error generating app icons: ${error.message}`) |
| 530 | + proc.exit(1) |
| 531 | + } |
| 532 | + }) |
| 533 | + |
435 | 534 | cli.version(version) |
436 | 535 | cli.help() |
437 | 536 | cli.parse() |
0 commit comments