diff --git a/apps/cli/src/commands/bundle.test.ts b/apps/cli/src/commands/bundle.test.ts index 1e781e6..67c68cd 100644 --- a/apps/cli/src/commands/bundle.test.ts +++ b/apps/cli/src/commands/bundle.test.ts @@ -244,6 +244,59 @@ describe('bundleCommand', () => { expect(console.log).toHaveBeenCalledWith(' ✅ Locales: en, fr, es'); }); + it('should suppress progress and success output in quiet mode', async () => { + await bundleCommand({ name: 'core', quiet: true }); + + expect(console.log).not.toHaveBeenCalled(); + }); + + it('should display warnings in quiet mode', async () => { + mockGenerateBundle.mockReturnValue({ + bundleKey: 'core', + filesGenerated: 3, + warnings: ['Warning 1'], + localesProcessed: ['en', 'fr'], + }); + + await bundleCommand({ name: 'core', quiet: true }); + + expect(console.log).toHaveBeenCalledTimes(1); + expect(console.log).toHaveBeenCalledWith(' ⚠️ Warnings: 1'); + expect(console.log).not.toHaveBeenCalledWith(' - Warning 1'); + }); + + it('should display errors in quiet mode', async () => { + mockGenerateBundle.mockImplementation(() => { + throw new Error('Bundle generation failed'); + }); + + await bundleCommand({ name: 'core', quiet: true }); + + expect(console.log).toHaveBeenCalledTimes(1); + expect(console.log).toHaveBeenCalledWith(' ❌ Bundle generation failed'); + }); + + it('should display type generation errors in quiet mode', async () => { + mockGenerateBundle.mockReturnValue({ + bundleKey: 'core', + filesGenerated: 3, + warnings: [], + localesProcessed: ['en'], + typeGenerationResult: { + bundleKey: 'core', + typeDistFile: 'src/generated/core-tokens.ts', + keysCount: 0, + fileGenerated: false, + errorReason: 'Unable to write type file', + }, + }); + + await bundleCommand({ name: 'core', quiet: true }); + + expect(console.log).toHaveBeenCalledTimes(1); + expect(console.log).toHaveBeenCalledWith(' └─ Types: Error (Unable to write type file)'); + }); + it('should display warnings count when warnings exist', async () => { mockGenerateBundle.mockReturnValue({ bundleKey: 'core', @@ -300,6 +353,31 @@ describe('bundleCommand', () => { expect(console.log).toHaveBeenCalledWith(' Total warnings: 1'); }); + it('should display warning totals in quiet mode for multiple bundles', async () => { + mockGenerateBundle + .mockReturnValueOnce({ + bundleKey: 'core', + filesGenerated: 3, + warnings: [], + localesProcessed: ['en', 'fr', 'es'], + }) + .mockReturnValueOnce({ + bundleKey: 'admin', + filesGenerated: 2, + warnings: ['Warning 1'], + localesProcessed: ['en', 'fr'], + }); + + await bundleCommand({ name: 'core,admin', quiet: true }); + + expect(console.log).not.toHaveBeenCalledWith('\n📊 Summary (2 bundles)'); + expect(console.log).not.toHaveBeenCalledWith(' Total files generated: 5'); + expect(console.log).toHaveBeenCalledWith(' Total warnings: 1'); + expect(console.log).toHaveBeenCalledWith(' Run with --verbose to see warning details'); + expect(console.log).toHaveBeenCalledWith(' ⚠️ Warnings: 1'); + expect(console.log).not.toHaveBeenCalledWith(' - Warning 1'); + }); + it('should display type generation success', async () => { mockGenerateBundle.mockReturnValue({ bundleKey: 'core', diff --git a/apps/cli/src/commands/bundle.ts b/apps/cli/src/commands/bundle.ts index 4c240c9..ee20f6e 100644 --- a/apps/cli/src/commands/bundle.ts +++ b/apps/cli/src/commands/bundle.ts @@ -6,6 +6,7 @@ import { loadConfiguration, parseCommaSeparatedList, ConsoleFormatter } from '.. export interface BundleOptions { name?: string; locale?: string; + quiet?: boolean; verbose?: boolean; /** CLI-level override for token casing. Takes precedence over all config file values. */ tokenCasing?: TokenCasing; @@ -92,9 +93,11 @@ export async function bundleCommand(options: BundleOptions): Promise { continue; } - console.log(''); - ConsoleFormatter.progress(`Generating bundle: ${bundleKey}`); - if (options.verbose && localeFilter) { + if (!options.quiet) { + console.log(''); + ConsoleFormatter.progress(`Generating bundle: ${bundleKey}`); + } + if (!options.quiet && options.verbose && localeFilter) { ConsoleFormatter.indent(`Locales: ${localeFilter.join(', ')}`); } @@ -117,30 +120,36 @@ export async function bundleCommand(options: BundleOptions): Promise { localesProcessed: result.localesProcessed, }); - ConsoleFormatter.indent(`✅ Files generated: ${result.filesGenerated}`); - ConsoleFormatter.indent(`✅ Locales: ${result.localesProcessed.join(', ')}`); + if (!options.quiet) { + ConsoleFormatter.indent(`✅ Files generated: ${result.filesGenerated}`); + ConsoleFormatter.indent(`✅ Locales: ${result.localesProcessed.join(', ')}`); + } if (result.typeGenerationResult) { if (result.typeGenerationResult.fileGenerated) { - ConsoleFormatter.indent( - `└─ Types: ${result.typeGenerationResult.typeDistFile} (${result.typeGenerationResult.keysCount} keys)`, - ); + if (!options.quiet) { + ConsoleFormatter.indent( + `└─ Types: ${result.typeGenerationResult.typeDistFile} (${result.typeGenerationResult.keysCount} keys)`, + ); + } } else if (result.typeGenerationResult.errorReason) { ConsoleFormatter.indent(`└─ Types: Error (${result.typeGenerationResult.errorReason})`); } else if (result.typeGenerationResult.skippedReason) { - const skippedReasonMessages: Record = { - 'empty-bundle': 'bundle has no keys', - 'not-configured': 'no typeDistFile configured', - }; - const skippedMessage = - skippedReasonMessages[result.typeGenerationResult.skippedReason] ?? - result.typeGenerationResult.skippedReason; - ConsoleFormatter.indent(`└─ Types: Skipped (${skippedMessage})`); + if (!options.quiet) { + const skippedReasonMessages: Record = { + 'empty-bundle': 'bundle has no keys', + 'not-configured': 'no typeDistFile configured', + }; + const skippedMessage = + skippedReasonMessages[result.typeGenerationResult.skippedReason] ?? + result.typeGenerationResult.skippedReason; + ConsoleFormatter.indent(`└─ Types: Skipped (${skippedMessage})`); + } } } else if (hasTypeDistConfigured(bundleDefinition)) { // Should have result if configured, but just in case ConsoleFormatter.indent(`└─ Types: Failed (No result returned)`); - } else { + } else if (!options.quiet) { ConsoleFormatter.indent(`└─ Types: Skipped (no typeDistFile configured)`); } @@ -182,8 +191,10 @@ export async function bundleCommand(options: BundleOptions): Promise { }, ); - ConsoleFormatter.section(`Summary (${totals.bundlesProcessed} bundles)`); - ConsoleFormatter.keyValue('Total files generated', totals.filesGenerated); + if (!options.quiet) { + ConsoleFormatter.section(`Summary (${totals.bundlesProcessed} bundles)`); + ConsoleFormatter.keyValue('Total files generated', totals.filesGenerated); + } if (totals.warningsCount > 0) { ConsoleFormatter.keyValue('Total warnings', totals.warningsCount); @@ -194,7 +205,9 @@ export async function bundleCommand(options: BundleOptions): Promise { const errors = bundleResults.filter((r) => r.error); if (errors.length > 0) { - console.log(''); + if (!options.quiet) { + console.log(''); + } ConsoleFormatter.warning(`${errors.length} bundle(s) failed to generate`); } } diff --git a/apps/cli/src/main.ts b/apps/cli/src/main.ts index 86b7195..faa0ff1 100644 --- a/apps/cli/src/main.ts +++ b/apps/cli/src/main.ts @@ -175,6 +175,7 @@ program .description('Generate translation bundles for deployment') .option('--name ', 'Bundle name(s) - single name or comma-separated (e.g., core,admin)') .option('--locale ', 'Locale(s) to generate - comma-separated (e.g., en,fr)') + .option('--quiet', 'Suppress progress and success output (warnings and errors are still shown)') .option('--verbose', 'Show detailed output including warnings') .addOption(new Option('--token-casing ', 'Token property key casing').choices(['upperCase', 'camelCase'])) .option( diff --git a/docs/cli.md b/docs/cli.md index d367602..7f0cb8c 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -903,6 +903,7 @@ lingo-tracker bundle [options] - `--token-constant-name ` - Custom name for the generated TypeScript constant. Must be a valid JavaScript identifier. Only works when targeting a single bundle (via `--name`). Overrides `tokenConstantName` in the bundle config - `--no-transform-icu-to-transloco` - Disable ICU to Transloco format conversion in bundle output - `--debug-keys [locale]` - Also emit a debug bundle where every value equals its own dot-delimited key. Useful for visually locating untranslated strings at runtime by switching your app to this locale. If no locale code is provided, defaults to `99` (e.g. `main.99.json`). ICU transformation is skipped for the debug bundle. +- `--quiet` - Suppress progress and success output (warnings and errors are still shown) - `--verbose` - Show detailed output including all warnings **What Bundle Generation Does:**