diff --git a/packages/rstack/src/fmt/cli.ts b/packages/rstack/src/fmt/cli.ts index ed61c36..40518c0 100644 --- a/packages/rstack/src/fmt/cli.ts +++ b/packages/rstack/src/fmt/cli.ts @@ -8,6 +8,7 @@ import { ensureProjectCacheDir } from '../projectCache.ts'; import { fmtCacheFileName } from './cacheStore.ts'; import { resolveFmtConfig } from './config.ts'; import { discoverFmtFiles } from './discovery.ts'; +import { formatDuration } from './duration.ts'; import { createRelativePathResolver, toPosixPath } from './pathHelpers.ts'; import { runFmtFiles } from './runner.ts'; import type { FmtMode, FmtRunResult, ResolvedFmtConfig } from './types.ts'; @@ -157,35 +158,6 @@ const createDisplayPathResolver = ( return (filePath) => toPosixPath(resolveRelativePath(filePath)); }; -const prettyTime = (seconds: number): string => { - const format = (time: string, unit: 'm' | 's') => - color.bold(`${time}${unit}`); - - if (seconds < 10) { - const digits = seconds >= 0.01 ? 2 : 3; - return format(seconds.toFixed(digits), 's'); - } - - if (seconds < 60) { - return format(seconds.toFixed(1), 's'); - } - - const minutes = Math.floor(seconds / 60); - const minutesLabel = format(minutes.toFixed(0), 'm'); - const remainingSeconds = seconds % 60; - - if (remainingSeconds === 0) { - return minutesLabel; - } - - const secondsLabel = format( - remainingSeconds.toFixed(remainingSeconds % 1 === 0 ? 0 : 1), - 's', - ); - - return `${minutesLabel} ${secondsLabel}`; -}; - const formatCount = (count: number): string => color.bold(count); const formatFileCount = (count: number, isError = false): string => { const formattedCount = formatCount(count); @@ -207,7 +179,7 @@ const logFmtResult = ( mode: FmtMode, cwd: string, processedFileCount: number, - durationSeconds: number, + durationMilliseconds: number, fixCommand?: string, ): void => { let writtenCount = 0; @@ -229,13 +201,18 @@ const logFmtResult = ( } } + if (mode === 'list-different') { + return; + } + + const time = color.bold(formatDuration(durationMilliseconds)); + if (mode === 'write') { if (writtenCount === 0 && result.exitCode !== 0) { return; } const processedFiles = formatFileCount(processedFileCount); - const time = prettyTime(durationSeconds); const message = writtenCount > 0 ? `Formatted ${formatCount(writtenCount)} of ${processedFiles} in ${time}.` @@ -244,10 +221,6 @@ const logFmtResult = ( return; } - if (mode !== 'check') { - return; - } - if (differentCount > 0) { const differentFiles = formatFileCount(differentCount, true); const processedFiles = formatFileCount(processedFileCount); @@ -255,10 +228,10 @@ const logFmtResult = ( ? `Run ${color.cyan(fixCommand)} to fix.` : `Rerun this command without ${color.cyan('--check')} to fix.`; logger.error(`Formatting issues found in ${differentFiles}. ${fixHint}`); - logger.info(`Checked ${processedFiles} in ${prettyTime(durationSeconds)}.`); + logger.info(`Checked ${processedFiles} in ${time}.`); } else if (result.exitCode === 0) { logger.success( - `Checked ${formatFileCount(processedFileCount)} in ${prettyTime(durationSeconds)}. No issues found.`, + `Checked ${formatFileCount(processedFileCount)} in ${time}. No issues found.`, ); } }; @@ -415,13 +388,13 @@ const runFmtCLI = async ( return; } - const durationSeconds = (performance.now() - startTime) / 1000; + const durationMilliseconds = performance.now() - startTime; logFmtResult( result, mode, cwd, result.processedFileCount, - durationSeconds, + durationMilliseconds, fixCommand, ); process.exitCode = result.exitCode; diff --git a/packages/rstack/src/fmt/duration.ts b/packages/rstack/src/fmt/duration.ts new file mode 100644 index 0000000..c093fab --- /dev/null +++ b/packages/rstack/src/fmt/duration.ts @@ -0,0 +1,34 @@ +/** Formats sub-second durations in milliseconds and preserves the existing longer-duration format. */ +const formatDuration = (milliseconds: number): string => { + if (milliseconds < 1) { + return '<1ms'; + } + + const roundedMilliseconds = Math.round(milliseconds); + if (roundedMilliseconds < 1000) { + return `${roundedMilliseconds}ms`; + } + + const seconds = milliseconds / 1000; + if (seconds < 10) { + return `${seconds.toFixed(2)}s`; + } + + if (seconds < 60) { + return `${seconds.toFixed(1)}s`; + } + + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + + if (remainingSeconds === 0) { + return `${minutes}m`; + } + + const secondsLabel = remainingSeconds.toFixed( + remainingSeconds % 1 === 0 ? 0 : 1, + ); + return `${minutes}m ${secondsLabel}s`; +}; + +export { formatDuration }; diff --git a/packages/rstack/tests/cli/fmt/helpers.ts b/packages/rstack/tests/cli/fmt/helpers.ts index d3b6222..5086ce5 100644 --- a/packages/rstack/tests/cli/fmt/helpers.ts +++ b/packages/rstack/tests/cli/fmt/helpers.ts @@ -42,7 +42,10 @@ export const createCliEnv = (): NodeJS.ProcessEnv => { }; export const normalizeDuration = (output: string): string => - output.replace(/\d+m(?: \d+(?:\.\d+)?s)?|\d+(?:\.\d+)?s/g, ''); + output.replace( + /<1ms|\d+ms|\d+m(?: \d+(?:\.\d+)?s)?|\d+(?:\.\d+)?s/g, + '', + ); export const expectWriteSummary = ( output: string, diff --git a/packages/rstack/tests/fmt/duration.test.ts b/packages/rstack/tests/fmt/duration.test.ts new file mode 100644 index 0000000..ad208cb --- /dev/null +++ b/packages/rstack/tests/fmt/duration.test.ts @@ -0,0 +1,19 @@ +import { expect, test } from 'rstack/test'; +import { formatDuration } from '../../src/fmt/duration.ts'; + +test.each([ + [0, '<1ms'], + [0.999, '<1ms'], + [1, '1ms'], + [29.6, '30ms'], + [999.4, '999ms'], + [999.6, '1.00s'], + [1_390, '1.39s'], + [1_234, '1.23s'], + [12_340, '12.3s'], + [60_000, '1m'], + [60_123, '1m 0.1s'], + [3_661_234, '61m 1.2s'], +] as const)('formats %sms as %s', (milliseconds, expected) => { + expect(formatDuration(milliseconds)).toBe(expected); +});