diff --git a/packages/plugin-eslint/src/lib/runner.integration.test.ts b/packages/plugin-eslint/src/lib/runner.integration.test.ts index ffe036223..cd97a78bd 100644 --- a/packages/plugin-eslint/src/lib/runner.integration.test.ts +++ b/packages/plugin-eslint/src/lib/runner.integration.test.ts @@ -1,5 +1,6 @@ import os from 'node:os'; import path from 'node:path'; +import process from 'node:process'; import { fileURLToPath } from 'node:url'; import { type MockInstance, describe, expect, it } from 'vitest'; import type { @@ -63,33 +64,36 @@ describe('executeRunner', () => { expect(osAgnosticAuditOutputs(json)).toMatchSnapshot(); }); - it('should execute runner with custom config using @code-pushup/eslint-config', async () => { - const runnerPaths = await createPluginConfig( - 'code-pushup.eslint.config.mjs', - ); - await executeRunner(runnerPaths); + it.skipIf(process.platform === 'win32')( + 'should execute runner with custom config using @code-pushup/eslint-config', + async () => { + const runnerPaths = await createPluginConfig( + 'code-pushup.eslint.config.mjs', + ); + await executeRunner(runnerPaths); - const json = await readJsonFile( - runnerPaths.runnerOutputPath, - ); - // expect warnings from unicorn/filename-case rule from default config - expect(json).toContainEqual( - expect.objectContaining>({ - slug: 'unicorn-filename-case', - displayValue: expect.stringMatching(/^\d+ warnings?$/), - details: { - issues: expect.arrayContaining([ - { - severity: 'warning', - message: - 'Filename is not in kebab case. Rename it to `use-todos.js`.', - source: expect.objectContaining({ - file: path.join(appDir, 'src', 'hooks', 'useTodos.js'), - }), - }, - ]), - }, - }), - ); - }); + const json = await readJsonFile( + runnerPaths.runnerOutputPath, + ); + // expect warnings from unicorn/filename-case rule from default config + expect(json).toContainEqual( + expect.objectContaining>({ + slug: 'unicorn-filename-case', + displayValue: expect.stringMatching(/^\d+ warnings?$/), + details: { + issues: expect.arrayContaining([ + { + severity: 'warning', + message: + 'Filename is not in kebab case. Rename it to `use-todos.js`.', + source: expect.objectContaining({ + file: path.join(appDir, 'src', 'hooks', 'useTodos.js'), + }), + }, + ]), + }, + }), + ); + }, + ); }, 20_000); diff --git a/packages/utils/src/lib/execute-process.ts b/packages/utils/src/lib/execute-process.ts index e82f6ba31..9e7fdfad6 100644 --- a/packages/utils/src/lib/execute-process.ts +++ b/packages/utils/src/lib/execute-process.ts @@ -1,4 +1,3 @@ -import { gray } from 'ansis'; import { type ChildProcess, type ChildProcessByStdio, @@ -7,7 +6,8 @@ import { spawn, } from 'node:child_process'; import type { Readable, Writable } from 'node:stream'; -import { ui } from './logging.js'; +import { formatCommandLog } from './format-command-log.js'; +import { isVerbose, ui } from './logging.js'; import { calcDuration } from './reports/utils.js'; /** @@ -150,12 +150,11 @@ export function executeProcess(cfg: ProcessConfig): Promise { const date = new Date().toISOString(); const start = performance.now(); - const logCommand = [command, ...(args || [])].join(' '); - ui().logger.log( - gray( - `Executing command:\n${logCommand}\nIn working directory:\n${cfg.cwd ?? process.cwd()}`, - ), - ); + if (isVerbose()) { + ui().logger.log( + formatCommandLog(command, args, `${cfg.cwd ?? process.cwd()}`), + ); + } return new Promise((resolve, reject) => { // shell:true tells Windows to use shell command for spawning a child process diff --git a/packages/utils/src/lib/format-command-log.integration.test.ts b/packages/utils/src/lib/format-command-log.integration.test.ts new file mode 100644 index 000000000..28a916a55 --- /dev/null +++ b/packages/utils/src/lib/format-command-log.integration.test.ts @@ -0,0 +1,61 @@ +import path from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { removeColorCodes } from '@code-pushup/test-utils'; +import { formatCommandLog } from './format-command-log.js'; + +describe('formatCommandLog', () => { + it('should format simple command', () => { + const result = removeColorCodes( + formatCommandLog('npx', ['command', '--verbose']), + ); + + expect(result).toBe('$ npx command --verbose'); + }); + + it('should format simple command with explicit process.cwd()', () => { + const result = removeColorCodes( + formatCommandLog('npx', ['command', '--verbose'], process.cwd()), + ); + + expect(result).toBe('$ npx command --verbose'); + }); + + it('should format simple command with relative cwd', () => { + const result = removeColorCodes( + formatCommandLog('npx', ['command', '--verbose'], './wololo'), + ); + + expect(result).toBe(`wololo $ npx command --verbose`); + }); + + it('should format simple command with absolute non-current path converted to relative', () => { + const result = removeColorCodes( + formatCommandLog( + 'npx', + ['command', '--verbose'], + path.join(process.cwd(), 'tmp'), + ), + ); + expect(result).toBe('tmp $ npx command --verbose'); + }); + + it('should format simple command with relative cwd in parent folder', () => { + const result = removeColorCodes( + formatCommandLog('npx', ['command', '--verbose'], '..'), + ); + + expect(result).toBe(`.. $ npx command --verbose`); + }); + + it('should format simple command using relative path to parent directory', () => { + const result = removeColorCodes( + formatCommandLog( + 'npx', + ['command', '--verbose'], + path.dirname(process.cwd()), + ), + ); + + expect(result).toBe('.. $ npx command --verbose'); + }); +}); diff --git a/packages/utils/src/lib/format-command-log.ts b/packages/utils/src/lib/format-command-log.ts new file mode 100644 index 000000000..0ce5a89cd --- /dev/null +++ b/packages/utils/src/lib/format-command-log.ts @@ -0,0 +1,27 @@ +import ansis from 'ansis'; +import path from 'node:path'; + +/** + * Formats a command string with optional cwd prefix and ANSI colors. + * + * @param {string} command - The command to execute. + * @param {string[]} args - Array of command arguments. + * @param {string} [cwd] - Optional current working directory for the command. + * @returns {string} - ANSI-colored formatted command string. + */ +export function formatCommandLog( + command: string, + args: string[] = [], + cwd: string = process.cwd(), +): string { + const relativeDir = path.relative(process.cwd(), cwd); + + return [ + ...(relativeDir && relativeDir !== '.' + ? [ansis.italic(ansis.gray(relativeDir))] + : []), + ansis.yellow('$'), + ansis.gray(command), + ansis.gray(args.map(arg => arg).join(' ')), + ].join(' '); +}