From 3d004eba24ff21a07147f88533882316c0e3f246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Chalk?= Date: Fri, 7 Nov 2025 17:32:36 +0100 Subject: [PATCH 1/4] feat(plugin-coverage): replace @poppinss/cliui with new logger --- packages/plugin-coverage/package.json | 1 - .../src/lib/nx/coverage-paths.ts | 20 +++++-------------- .../plugin-coverage/src/lib/runner/index.ts | 13 ------------ .../src/lib/runner/lcov/lcov-runner.ts | 6 ++---- .../lib/runner/lcov/lcov-runner.unit.test.ts | 7 +++---- 5 files changed, 10 insertions(+), 37 deletions(-) diff --git a/packages/plugin-coverage/package.json b/packages/plugin-coverage/package.json index 7c5619b8a..88231bff6 100644 --- a/packages/plugin-coverage/package.json +++ b/packages/plugin-coverage/package.json @@ -36,7 +36,6 @@ "dependencies": { "@code-pushup/models": "0.85.0", "@code-pushup/utils": "0.85.0", - "ansis": "^3.3.0", "parse-lcov": "^1.0.4", "yargs": "^17.7.2", "zod": "^4.0.5" diff --git a/packages/plugin-coverage/src/lib/nx/coverage-paths.ts b/packages/plugin-coverage/src/lib/nx/coverage-paths.ts index 5f587e33b..12a708134 100644 --- a/packages/plugin-coverage/src/lib/nx/coverage-paths.ts +++ b/packages/plugin-coverage/src/lib/nx/coverage-paths.ts @@ -6,9 +6,8 @@ import type { } from '@nx/devkit'; import type { JestExecutorOptions } from '@nx/jest/src/executors/jest/schema'; import type { VitestExecutorOptions } from '@nx/vite/executors'; -import { bold } from 'ansis'; import path from 'node:path'; -import { importModule, stringifyError, ui } from '@code-pushup/utils'; +import { importModule, logger, stringifyError } from '@code-pushup/utils'; import type { CoverageResult } from '../config.js'; /** @@ -22,7 +21,7 @@ async function resolveCachedProjectGraph() { try { return readCachedProjectGraph(); } catch (error) { - ui().logger.info( + logger.info( `Could not read cached project graph, falling back to async creation. ${stringifyError(error)}`, ); @@ -36,13 +35,8 @@ async function resolveCachedProjectGraph() { */ export async function getNxCoveragePaths( targets: string[] = ['test'], - verbose?: boolean, ): Promise { - if (verbose) { - ui().logger.info( - bold('💡 Gathering coverage from the following nx projects:'), - ); - } + logger.debug('💡 Gathering coverage from the following nx projects:'); const { nodes } = await resolveCachedProjectGraph(); @@ -55,18 +49,14 @@ export async function getNxCoveragePaths( return await Promise.all( relevantNodes.map>(async ({ name, data }) => { const coveragePaths = await getCoveragePathsForTarget(data, target); - if (verbose) { - ui().logger.info(`- ${name}: ${target}`); - } + logger.debug(`- ${name}: ${target}`); return coveragePaths; }), ); }), ); - if (verbose) { - ui().logger.info('\n'); - } + logger.debug(''); return coverageResults.flat(); } diff --git a/packages/plugin-coverage/src/lib/runner/index.ts b/packages/plugin-coverage/src/lib/runner/index.ts index f22ed5476..79dadcb1b 100644 --- a/packages/plugin-coverage/src/lib/runner/index.ts +++ b/packages/plugin-coverage/src/lib/runner/index.ts @@ -1,16 +1,13 @@ -import { bold } from 'ansis'; import { writeFile } from 'node:fs/promises'; import path from 'node:path'; import type { RunnerConfig, RunnerFilesPaths } from '@code-pushup/models'; import { - ProcessError, createRunnerFiles, ensureDirectoryExists, executeProcess, filePathToCliArg, objectToCliArgs, readJsonFile, - ui, } from '@code-pushup/utils'; import type { FinalCoveragePluginConfig } from '../config.js'; import { lcovResultsToAuditOutputs } from './lcov/lcov-runner.js'; @@ -28,16 +25,6 @@ export async function executeRunner({ try { await executeProcess({ command, args }); } catch (error) { - if (error instanceof ProcessError) { - const loggingFn = continueOnCommandFail - ? ui().logger.warning.bind(ui().logger) - : ui().logger.error.bind(ui().logger); - loggingFn(bold('stdout from failed coverage tool process:')); - loggingFn(error.stdout); - loggingFn(bold('stderr from failed coverage tool process:')); - loggingFn(error.stderr); - } - if (!continueOnCommandFail) { throw new Error( 'Coverage plugin: Running coverage tool failed. Make sure all your provided tests are passing.', diff --git a/packages/plugin-coverage/src/lib/runner/lcov/lcov-runner.ts b/packages/plugin-coverage/src/lib/runner/lcov/lcov-runner.ts index eb5e34ccb..ad9ae4ff2 100644 --- a/packages/plugin-coverage/src/lib/runner/lcov/lcov-runner.ts +++ b/packages/plugin-coverage/src/lib/runner/lcov/lcov-runner.ts @@ -5,11 +5,11 @@ import { type FileCoverage, exists, getGitRoot, + logger, objectFromEntries, objectToEntries, readTextFile, toUnixNewlines, - ui, } from '@code-pushup/utils'; import type { CoverageResult, CoverageType } from '../../config.js'; import { mergeLcovResults } from './merge-lcov.js'; @@ -72,9 +72,7 @@ export async function parseLcovFiles( typeof result === 'string' ? result : result.resultsPath; const lcovFileContent = await readTextFile(resultsPath); if (lcovFileContent.trim() === '') { - ui().logger.warning( - `Coverage plugin: Empty lcov report file detected at ${resultsPath}.`, - ); + logger.warn(`Empty lcov report file detected at ${resultsPath}.`); } const parsedRecords = parseLcov(toUnixNewlines(lcovFileContent)); return parsedRecords.map( diff --git a/packages/plugin-coverage/src/lib/runner/lcov/lcov-runner.unit.test.ts b/packages/plugin-coverage/src/lib/runner/lcov/lcov-runner.unit.test.ts index b943b04f0..49c40959f 100644 --- a/packages/plugin-coverage/src/lib/runner/lcov/lcov-runner.unit.test.ts +++ b/packages/plugin-coverage/src/lib/runner/lcov/lcov-runner.unit.test.ts @@ -1,7 +1,7 @@ import { vol } from 'memfs'; import path from 'node:path'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { getGitRoot, ui } from '@code-pushup/utils'; +import { getGitRoot, logger } from '@code-pushup/utils'; import type { CoverageResult, CoverageType } from '../../config.js'; import { lcovResultsToAuditOutputs, parseLcovFiles } from './lcov-runner.js'; @@ -144,9 +144,8 @@ end_of_record path.join('coverage', 'lcov.info'), ]); - expect(ui()).toHaveLogged( - 'warn', - `Coverage plugin: Empty lcov report file detected at ${path.join( + expect(logger.warn).toHaveBeenCalledWith( + `Empty lcov report file detected at ${path.join( 'coverage', 'lcov.info', )}.`, From ef87c62c715e0edc46617812b7d54413b3c156cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Chalk?= Date: Fri, 7 Nov 2025 17:33:15 +0100 Subject: [PATCH 2/4] feat(plugin-eslint): replace @poppinss/cliui with new logger --- packages/plugin-eslint/src/lib/meta/groups.ts | 4 ++-- packages/plugin-eslint/src/lib/meta/groups.unit.test.ts | 5 ++--- packages/plugin-eslint/src/lib/meta/versions/flat.ts | 6 +++--- packages/plugin-eslint/src/lib/meta/versions/legacy.ts | 4 ++-- packages/plugin-eslint/src/lib/nx/find-all-projects.ts | 7 +++---- packages/plugin-eslint/src/lib/runner/index.ts | 4 ++-- packages/plugin-eslint/src/lib/runner/index.unit.test.ts | 6 ++++-- packages/plugin-eslint/src/lib/runner/transform.ts | 6 ++---- 8 files changed, 20 insertions(+), 22 deletions(-) diff --git a/packages/plugin-eslint/src/lib/meta/groups.ts b/packages/plugin-eslint/src/lib/meta/groups.ts index d0d82a36d..9e5616c96 100644 --- a/packages/plugin-eslint/src/lib/meta/groups.ts +++ b/packages/plugin-eslint/src/lib/meta/groups.ts @@ -1,6 +1,6 @@ import type { Rule } from 'eslint'; import type { Group, GroupRef } from '@code-pushup/models'; -import { objectToKeys, slugify, ui } from '@code-pushup/utils'; +import { logger, objectToKeys, slugify } from '@code-pushup/utils'; import type { CustomGroup } from '../config.js'; import { ruleToSlug } from './hash.js'; import { type RuleData, parseRuleId } from './parse.js'; @@ -109,7 +109,7 @@ export function groupsFromCustomConfig( `Invalid rule configuration in group ${group.slug}. All rules are invalid.`, ); } - ui().logger.warning( + logger.warn( `Some rules in group ${group.slug} are invalid: ${invalidRules.join(', ')}`, ); } diff --git a/packages/plugin-eslint/src/lib/meta/groups.unit.test.ts b/packages/plugin-eslint/src/lib/meta/groups.unit.test.ts index 4c1fb9179..b80d1f6e3 100644 --- a/packages/plugin-eslint/src/lib/meta/groups.unit.test.ts +++ b/packages/plugin-eslint/src/lib/meta/groups.unit.test.ts @@ -1,5 +1,5 @@ import type { Group } from '@code-pushup/models'; -import { ui } from '@code-pushup/utils'; +import { logger } from '@code-pushup/utils'; import { createRulesMap, groupsFromCustomConfig, @@ -306,8 +306,7 @@ describe('groupsFromCustomConfig', () => { refs: [{ slug: 'react-jsx-key', weight: 3 }], }, ]); - expect(ui()).toHaveLogged( - 'warn', + expect(logger.warn).toHaveBeenCalledWith( 'Some rules in group custom-group are invalid: invalid-rule', ); }); diff --git a/packages/plugin-eslint/src/lib/meta/versions/flat.ts b/packages/plugin-eslint/src/lib/meta/versions/flat.ts index 80e9377ca..5e3029312 100644 --- a/packages/plugin-eslint/src/lib/meta/versions/flat.ts +++ b/packages/plugin-eslint/src/lib/meta/versions/flat.ts @@ -2,7 +2,7 @@ import type { Linter, Rule } from 'eslint'; import { builtinRules } from 'eslint/use-at-your-own-risk'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; -import { exists, findNearestFile, toArray, ui } from '@code-pushup/utils'; +import { exists, findNearestFile, logger, toArray } from '@code-pushup/utils'; import type { ESLintTarget } from '../../config.js'; import { jsonHash } from '../hash.js'; import { @@ -25,7 +25,7 @@ export async function loadRulesForFlatConfig({ .map(rule => { const meta = findRuleMeta(rule.id, configs); if (!meta) { - ui().logger.warning(`Cannot find metadata for rule ${rule.id}`); + logger.warn(`Cannot find metadata for rule ${rule.id}`); return null; } return { ...rule, meta }; @@ -106,7 +106,7 @@ function findPluginRuleMeta( const rule = config?.plugins?.[plugin]?.rules?.[name]; if (typeof rule === 'function') { - ui().logger.warning( + logger.warn( `Cannot parse metadata for rule ${plugin}/${name}, plugin registers it as a function`, ); return undefined; diff --git a/packages/plugin-eslint/src/lib/meta/versions/legacy.ts b/packages/plugin-eslint/src/lib/meta/versions/legacy.ts index 76fb25e7b..807ff93e5 100644 --- a/packages/plugin-eslint/src/lib/meta/versions/legacy.ts +++ b/packages/plugin-eslint/src/lib/meta/versions/legacy.ts @@ -3,8 +3,8 @@ import { asyncSequential, distinct, exists, + logger, toArray, - ui, } from '@code-pushup/utils'; import type { ESLintTarget } from '../../config.js'; import { setupESLint } from '../../setup.js'; @@ -41,7 +41,7 @@ export async function loadRulesForLegacyConfig({ } const ruleMeta = rulesMeta[id]; if (!ruleMeta) { - ui().logger.warning(`Metadata not found for ESLint rule ${id}`); + logger.warn(`Metadata not found for ESLint rule ${id}`); return null; } // ignoring meta.defaultOptions to match legacy config handling in calculateConfigForFile diff --git a/packages/plugin-eslint/src/lib/nx/find-all-projects.ts b/packages/plugin-eslint/src/lib/nx/find-all-projects.ts index 10306f83e..e56db7f9c 100644 --- a/packages/plugin-eslint/src/lib/nx/find-all-projects.ts +++ b/packages/plugin-eslint/src/lib/nx/find-all-projects.ts @@ -1,4 +1,4 @@ -import { stringifyError, ui } from '@code-pushup/utils'; +import { logger, stringifyError } from '@code-pushup/utils'; import type { ESLintTarget } from '../config.js'; import { filterProjectGraph } from './filter-project-graph.js'; import { nxProjectsToConfig } from './projects-to-config.js'; @@ -14,9 +14,8 @@ async function resolveCachedProjectGraph() { try { return readCachedProjectGraph(); } catch (error) { - ui().logger.info( - `Could not read cached project graph, falling back to async creation. - ${stringifyError(error)}`, + logger.info( + `Could not read cached project graph, falling back to async creation.\n${stringifyError(error)}`, ); return await createProjectGraphAsync({ exitOnError: false }); } diff --git a/packages/plugin-eslint/src/lib/runner/index.ts b/packages/plugin-eslint/src/lib/runner/index.ts index 5d95e9981..d6c34472d 100644 --- a/packages/plugin-eslint/src/lib/runner/index.ts +++ b/packages/plugin-eslint/src/lib/runner/index.ts @@ -5,7 +5,7 @@ import type { PluginArtifactOptions, RunnerFunction, } from '@code-pushup/models'; -import { asyncSequential, ui } from '@code-pushup/utils'; +import { asyncSequential, logger } from '@code-pushup/utils'; import type { ESLintPluginRunnerConfig, ESLintTarget } from '../config.js'; import { lint } from './lint.js'; import { lintResultsToAudits, mergeLinterOutputs } from './transform.js'; @@ -23,7 +23,7 @@ export function createRunnerFunction(options: { }; return async (): Promise => { - ui().logger.log(`ESLint plugin executing ${targets.length} lint targets`); + logger.info(`ESLint plugin executing ${targets.length} lint targets`); const linterOutputs = artifacts ? await loadArtifacts(artifacts) diff --git a/packages/plugin-eslint/src/lib/runner/index.unit.test.ts b/packages/plugin-eslint/src/lib/runner/index.unit.test.ts index 52f38bef9..3ecf1f5ad 100644 --- a/packages/plugin-eslint/src/lib/runner/index.unit.test.ts +++ b/packages/plugin-eslint/src/lib/runner/index.unit.test.ts @@ -6,7 +6,7 @@ import { DEFAULT_PERSIST_CONFIG, type pluginArtifactOptionsSchema, } from '@code-pushup/models'; -import { ui } from '@code-pushup/utils'; +import { logger } from '@code-pushup/utils'; import type { ESLintTarget } from '../config.js'; import { createRunnerFunction } from './index.js'; import * as lintModule from './lint.js'; @@ -130,7 +130,9 @@ describe('createRunnerFunction', () => { expect(loadArtifactsSpy).toHaveBeenCalledWith(artifacts); expect(lintSpy).not.toHaveBeenCalled(); - expect(ui()).toHaveLogged('log', 'ESLint plugin executing 2 lint targets'); + expect(logger.info).toHaveBeenCalledWith( + 'ESLint plugin executing 2 lint targets', + ); }); it('should use internal linting logic when artifacts are not provided', async () => { diff --git a/packages/plugin-eslint/src/lib/runner/transform.ts b/packages/plugin-eslint/src/lib/runner/transform.ts index 7d30e35ce..3ed309430 100644 --- a/packages/plugin-eslint/src/lib/runner/transform.ts +++ b/packages/plugin-eslint/src/lib/runner/transform.ts @@ -3,10 +3,10 @@ import type { AuditOutput, Issue, IssueSeverity } from '@code-pushup/models'; import { compareIssueSeverity, countOccurrences, + logger, objectToEntries, pluralizeToken, truncateIssueMessage, - ui, } from '@code-pushup/utils'; import { ruleIdToSlug } from '../meta/index.js'; import type { LinterOutput } from './types.js'; @@ -36,9 +36,7 @@ export function lintResultsToAudits({ .reduce>((acc, issue) => { const { ruleId, message, filePath } = issue; if (!ruleId) { - ui().logger.warning( - `ESLint core error - ${message} (file: ${filePath})`, - ); + logger.warn(`ESLint core error - ${message} (file: ${filePath})`); return acc; } const options = ruleOptionsPerFile[filePath]?.[ruleId] ?? []; From a03b58fafbe695aded4d73099e7c0d0401581472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Chalk?= Date: Fri, 7 Nov 2025 17:34:26 +0100 Subject: [PATCH 3/4] feat(plugin-lighthouse): replace @poppinss/cliui with new logger --- .../src/lib/normalize-flags.ts | 11 +++---- .../src/lib/normalize-flags.unit.test.ts | 24 ++++++-------- .../src/lib/runner/details/details.ts | 11 +++---- .../lib/runner/details/details.unit.test.ts | 22 +++++-------- .../src/lib/runner/details/item-value.ts | 14 ++++----- .../runner/details/item-value.unit.test.ts | 24 ++++++-------- .../src/lib/runner/details/utils.ts | 6 ++-- .../src/lib/runner/runner.ts | 13 ++++++-- .../src/lib/runner/runner.unit.test.ts | 31 ++++++++++--------- .../plugin-lighthouse/src/lib/runner/utils.ts | 10 +++--- .../src/lib/runner/utils.unit.test.ts | 10 +++--- 11 files changed, 82 insertions(+), 94 deletions(-) diff --git a/packages/plugin-lighthouse/src/lib/normalize-flags.ts b/packages/plugin-lighthouse/src/lib/normalize-flags.ts index a5d45e62f..623e43937 100644 --- a/packages/plugin-lighthouse/src/lib/normalize-flags.ts +++ b/packages/plugin-lighthouse/src/lib/normalize-flags.ts @@ -1,6 +1,5 @@ -import { bold, yellow } from 'ansis'; -import { ui } from '@code-pushup/utils'; -import { LIGHTHOUSE_PLUGIN_SLUG } from './constants.js'; +import ansis from 'ansis'; +import { logger } from '@code-pushup/utils'; import { DEFAULT_CLI_FLAGS } from './runner/constants.js'; import type { LighthouseCliFlags } from './runner/types.js'; import type { LighthouseOptions } from './types.js'; @@ -73,10 +72,8 @@ export function logUnsupportedFlagsInUse( if (unsupportedFlagsInUse.length > 0) { const postFix = (count: number) => count > displayCount ? ` and ${count - displayCount} more.` : ''; - ui().logger.debug( - `${yellow('⚠')} Plugin ${bold( - LIGHTHOUSE_PLUGIN_SLUG, - )} used unsupported flags: ${bold( + logger.warn( + `Used unsupported flags: ${ansis.bold( unsupportedFlagsInUse.slice(0, displayCount).join(', '), )}${postFix(unsupportedFlagsInUse.length)}`, ); diff --git a/packages/plugin-lighthouse/src/lib/normalize-flags.unit.test.ts b/packages/plugin-lighthouse/src/lib/normalize-flags.unit.test.ts index 39e711976..e0f71c201 100644 --- a/packages/plugin-lighthouse/src/lib/normalize-flags.unit.test.ts +++ b/packages/plugin-lighthouse/src/lib/normalize-flags.unit.test.ts @@ -1,7 +1,7 @@ -import { bold, yellow } from 'ansis'; +import ansis from 'ansis'; import path from 'node:path'; import { describe, expect, it } from 'vitest'; -import { ui } from '@code-pushup/utils'; +import { logger } from '@code-pushup/utils'; import { DEFAULT_CHROME_FLAGS, LIGHTHOUSE_OUTPUT_PATH } from './constants.js'; import { logUnsupportedFlagsInUse, normalizeFlags } from './normalize-flags.js'; import { LIGHTHOUSE_REPORT_NAME } from './runner/constants.js'; @@ -10,12 +10,9 @@ import type { LighthouseOptions } from './types.js'; describe('logUnsupportedFlagsInUse', () => { it('should log unsupported entries', () => { logUnsupportedFlagsInUse({ 'list-all-audits': true } as LighthouseOptions); - expect(ui()).toHaveLoggedTimes(1); - expect(ui()).toHaveLogged( - 'debug', - `${yellow('⚠')} Plugin ${bold( - 'lighthouse', - )} used unsupported flags: ${bold('list-all-audits')}`, + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith( + `Used unsupported flags: ${ansis.bold('list-all-audits')}`, ); }); @@ -32,12 +29,9 @@ describe('logUnsupportedFlagsInUse', () => { // unsupported ...unsupportedFlags, } as unknown as LighthouseOptions); - expect(ui()).toHaveLoggedTimes(1); - expect(ui()).toHaveLogged( - 'debug', - `${yellow('⚠')} Plugin ${bold( - 'lighthouse', - )} used unsupported flags: ${bold( + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith( + `Used unsupported flags: ${ansis.bold( 'list-all-audits, list-locales, list-trace-categories', )} and 3 more.`, ); @@ -119,7 +113,7 @@ describe('normalizeFlags', () => { ...supportedFlags, } as unknown as LighthouseOptions), ).toEqual(expect.not.objectContaining({ 'list-all-audits': true })); - expect(ui()).toHaveLoggedTimes(1); + expect(logger.warn).toHaveBeenCalledTimes(1); }); it('should remove any flag with an empty array as a value', () => { diff --git a/packages/plugin-lighthouse/src/lib/runner/details/details.ts b/packages/plugin-lighthouse/src/lib/runner/details/details.ts index c58c7afe4..84cadf6d1 100644 --- a/packages/plugin-lighthouse/src/lib/runner/details/details.ts +++ b/packages/plugin-lighthouse/src/lib/runner/details/details.ts @@ -1,10 +1,9 @@ -import { bold, yellow } from 'ansis'; +import ansis from 'ansis'; import type { FormattedIcu } from 'lighthouse'; import type Details from 'lighthouse/types/lhr/audit-details'; import type { Result } from 'lighthouse/types/lhr/audit-result'; import type { AuditDetails } from '@code-pushup/models'; -import { ui } from '@code-pushup/utils'; -import { PLUGIN_SLUG } from '../constants.js'; +import { logger } from '@code-pushup/utils'; import { parseCriticalRequestChainToAuditDetails } from './critical-request-chain.type.js'; import { parseOpportunityToAuditDetailsTable } from './opportunity.type.js'; import { parseTableToAuditDetailsTable } from './table.type.js'; @@ -58,10 +57,8 @@ export function logUnsupportedDetails(lhrAudits: Result[]) { ), ]; if (slugsWithDetailParsingErrors.length > 0) { - ui().logger.debug( - `${yellow('⚠')} Plugin ${bold( - PLUGIN_SLUG, - )} skipped parsing of unsupported audit details: ${bold( + logger.warn( + `Skipped parsing of unsupported audit details: ${ansis.bold( slugsWithDetailParsingErrors.join(', '), )}.`, ); diff --git a/packages/plugin-lighthouse/src/lib/runner/details/details.unit.test.ts b/packages/plugin-lighthouse/src/lib/runner/details/details.unit.test.ts index fecce3275..30a2bd794 100644 --- a/packages/plugin-lighthouse/src/lib/runner/details/details.unit.test.ts +++ b/packages/plugin-lighthouse/src/lib/runner/details/details.unit.test.ts @@ -1,21 +1,18 @@ -import { bold, yellow } from 'ansis'; +import ansis from 'ansis'; import type { FormattedIcu } from 'lighthouse'; import type Details from 'lighthouse/types/lhr/audit-details'; import type { Result } from 'lighthouse/types/lhr/audit-result'; import { describe, expect, it } from 'vitest'; import type { AuditDetails } from '@code-pushup/models'; -import { ui } from '@code-pushup/utils'; +import { logger } from '@code-pushup/utils'; import { logUnsupportedDetails, toAuditDetails } from './details.js'; describe('logUnsupportedDetails', () => { it('should log unsupported entries', () => { logUnsupportedDetails([{ details: { type: 'screenshot' } }] as Result[]); - expect(ui()).toHaveLoggedTimes(1); - expect(ui()).toHaveLogged( - 'debug', - `${yellow('⚠')} Plugin ${bold( - 'lighthouse', - )} skipped parsing of unsupported audit details: ${bold('screenshot')}.`, + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith( + `Skipped parsing of unsupported audit details: ${ansis.bold('screenshot')}.`, ); }); @@ -29,12 +26,9 @@ describe('logUnsupportedDetails', () => { { details: { type: 'treemap-data' } }, { details: { type: 'criticalrequestchain' } }, ] as Result[]); - expect(ui()).toHaveLoggedTimes(1); - expect(ui()).toHaveLogged( - 'debug', - `${yellow('⚠')} Plugin ${bold( - 'lighthouse', - )} skipped parsing of unsupported audit details: ${bold( + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith( + `Skipped parsing of unsupported audit details: ${ansis.bold( 'filmstrip, screenshot, debugdata', )}.`, ); diff --git a/packages/plugin-lighthouse/src/lib/runner/details/item-value.ts b/packages/plugin-lighthouse/src/lib/runner/details/item-value.ts index 1e2890af4..6d4b1e3c4 100644 --- a/packages/plugin-lighthouse/src/lib/runner/details/item-value.ts +++ b/packages/plugin-lighthouse/src/lib/runner/details/item-value.ts @@ -1,13 +1,13 @@ -import { bold } from 'ansis'; +import ansis from 'ansis'; import type { IcuMessage } from 'lighthouse'; import type Details from 'lighthouse/types/lhr/audit-details'; import { formatBytes, formatDuration, html, + logger, roundDecimals, truncateText, - ui, } from '@code-pushup/utils'; export type PrimitiveItemValue = string | number | boolean; @@ -85,11 +85,11 @@ export function formatTableItemPropertyValue( return truncateText(String(parsedItemValue), 500); case 'multi': // @TODO // @TODO log verbose first, then implement data type - ui().logger.info(`Format type ${bold('multi')} is not implemented`); + logger.debug(`Format type ${ansis.bold('multi')} is not implemented`); return ''; case 'thumbnail': // @TODO // @TODO log verbose first, then implement data type - ui().logger.info(`Format type ${bold('thumbnail')} is not implemented`); + logger.debug(`Format type ${ansis.bold('thumbnail')} is not implemented`); return ''; } /* eslint-enable @typescript-eslint/no-magic-numbers */ @@ -145,13 +145,11 @@ export function parseTableItemPropertyValue( return String(url); case 'subitems': // @TODO log verbose first, then implement data type - ui().logger.info(`Value type ${bold('subitems')} is not implemented`); + logger.debug(`Value type ${ansis.bold('subitems')} is not implemented`); return ''; case 'debugdata': // @TODO log verbose first, then implement data type - ui().logger.info(`Value type ${bold('debugdata')} is not implemented`, { - silent: true, - }); + logger.debug(`Value type ${ansis.bold('debugdata')} is not implemented`); return ''; } // IcuMessage diff --git a/packages/plugin-lighthouse/src/lib/runner/details/item-value.unit.test.ts b/packages/plugin-lighthouse/src/lib/runner/details/item-value.unit.test.ts index fe50f6748..0bb3ad394 100644 --- a/packages/plugin-lighthouse/src/lib/runner/details/item-value.unit.test.ts +++ b/packages/plugin-lighthouse/src/lib/runner/details/item-value.unit.test.ts @@ -1,7 +1,7 @@ -import { bold } from 'ansis'; +import ansis from 'ansis'; import type Details from 'lighthouse/types/lhr/audit-details'; import { beforeAll, describe, expect, it } from 'vitest'; -import { ui } from '@code-pushup/utils'; +import { logger, ui } from '@code-pushup/utils'; import { type SimpleItemValue, formatTableItemPropertyValue, @@ -146,17 +146,15 @@ describe('parseTableItemPropertyValue', () => { }), ).toBe(''); - expect(ui()).toHaveLogged( - 'info', - `Value type ${bold('subitems')} is not implemented`, + expect(logger.debug).toHaveBeenCalledWith( + `Value type ${ansis.bold('subitems')} is not implemented`, ); }); it('should parse value item debugdata to empty string and log implemented', () => { expect(parseTableItemPropertyValue({ type: 'debugdata' })).toBe(''); - expect(ui()).toHaveLogged( - 'info', - `Value type ${bold('debugdata')} is not implemented`, + expect(logger.debug).toHaveBeenCalledWith( + `Value type ${ansis.bold('debugdata')} is not implemented`, ); }); @@ -363,9 +361,8 @@ describe('formatTableItemPropertyValue', () => { ), ).toBe(''); - expect(ui()).toHaveLogged( - 'info', - `Format type ${bold('multi')} is not implemented`, + expect(logger.debug).toHaveBeenCalledWith( + `Format type ${ansis.bold('multi')} is not implemented`, ); }); @@ -376,9 +373,8 @@ describe('formatTableItemPropertyValue', () => { 'thumbnail', ), ).toBe(''); - expect(ui()).toHaveLogged( - 'info', - `Format type ${bold('thumbnail')} is not implemented`, + expect(logger.debug).toHaveBeenCalledWith( + `Format type ${ansis.bold('thumbnail')} is not implemented`, ); }); diff --git a/packages/plugin-lighthouse/src/lib/runner/details/utils.ts b/packages/plugin-lighthouse/src/lib/runner/details/utils.ts index f467224f0..816a976a7 100644 --- a/packages/plugin-lighthouse/src/lib/runner/details/utils.ts +++ b/packages/plugin-lighthouse/src/lib/runner/details/utils.ts @@ -1,4 +1,4 @@ -import { bold } from 'ansis'; +import ansis from 'ansis'; import type Details from 'lighthouse/types/lhr/audit-details'; import { stringifyError } from '@code-pushup/utils'; @@ -9,9 +9,9 @@ export class LighthouseAuditDetailsParsingError extends Error { error: unknown, ) { super( - `Parsing lighthouse report details ${bold( + `Parsing lighthouse report details ${ansis.bold( type, - )} failed: \nRaw data:\n ${JSON.stringify(rawTable, null, 2)}\n${stringifyError(error)}`, + )} failed:\nRaw data:\n${JSON.stringify(rawTable, null, 2)}\n${stringifyError(error)}`, ); } } diff --git a/packages/plugin-lighthouse/src/lib/runner/runner.ts b/packages/plugin-lighthouse/src/lib/runner/runner.ts index 252389d00..c9921fd7c 100644 --- a/packages/plugin-lighthouse/src/lib/runner/runner.ts +++ b/packages/plugin-lighthouse/src/lib/runner/runner.ts @@ -2,7 +2,12 @@ import type { Config, RunnerResult } from 'lighthouse'; import { runLighthouse } from 'lighthouse/cli/run.js'; import path from 'node:path'; import type { AuditOutputs, RunnerFunction } from '@code-pushup/models'; -import { ensureDirectoryExists, stringifyError, ui } from '@code-pushup/utils'; +import { + ensureDirectoryExists, + link, + logger, + stringifyError, +} from '@code-pushup/utils'; import { orderSlug, shouldExpandForUrls } from '../processing.js'; import type { LighthouseOptions } from '../types.js'; import { DEFAULT_CLI_FLAGS } from './constants.js'; @@ -46,7 +51,7 @@ export function createRunnerFunction( return [...acc, ...processedOutputs]; } catch (error) { - ui().logger.warning(stringifyError(error)); + logger.warn(stringifyError(error)); return acc; } }, Promise.resolve([])); @@ -74,7 +79,9 @@ async function runLighthouseForUrl( const runnerResult: unknown = await runLighthouse(url, flags, config); if (runnerResult == null) { - throw new Error(`Lighthouse did not produce a result for URL: ${url}`); + throw new Error( + `Lighthouse did not produce a result for URL: ${link(url)}`, + ); } const { lhr } = runnerResult as RunnerResult; diff --git a/packages/plugin-lighthouse/src/lib/runner/runner.unit.test.ts b/packages/plugin-lighthouse/src/lib/runner/runner.unit.test.ts index 7774df24f..d997d4e0d 100644 --- a/packages/plugin-lighthouse/src/lib/runner/runner.unit.test.ts +++ b/packages/plugin-lighthouse/src/lib/runner/runner.unit.test.ts @@ -1,8 +1,10 @@ +import ansis from 'ansis'; import type { Config } from 'lighthouse'; import { runLighthouse } from 'lighthouse/cli/run.js'; import type { Result } from 'lighthouse/types/lhr/audit-result'; import { expect, vi } from 'vitest'; -import { ui } from '@code-pushup/utils'; +import { DEFAULT_PERSIST_CONFIG } from '@code-pushup/models'; +import { logger } from '@code-pushup/utils'; import { DEFAULT_CLI_FLAGS } from './constants.js'; import { createRunnerFunction } from './runner.js'; import type { LighthouseCliFlags } from './types.js'; @@ -62,9 +64,11 @@ vi.mock('lighthouse/cli/run.js', async () => { }); describe('createRunnerFunction', () => { + const args = { persist: DEFAULT_PERSIST_CONFIG }; + it('should call runLighthouse with defaults when executed with only url given', async () => { const runner = createRunnerFunction(['https://localhost:8080']); - await expect(runner(undefined)).resolves.toEqual( + await expect(runner(args)).resolves.toEqual( expect.arrayContaining([ { slug: 'cumulative-layout-shift', @@ -83,7 +87,7 @@ describe('createRunnerFunction', () => { }); it('should call enrichFlags with correct parameters for single URL', async () => { - await createRunnerFunction(['https://localhost:8080'])(undefined); + await createRunnerFunction(['https://localhost:8080'])(args); expect(enrichFlags).toHaveBeenCalledWith(DEFAULT_CLI_FLAGS); }); @@ -92,7 +96,7 @@ describe('createRunnerFunction', () => { await createRunnerFunction([ 'https://localhost:8080', 'https://localhost:8081', - ])(undefined); + ])(args); expect(enrichFlags).toHaveBeenCalledWith(DEFAULT_CLI_FLAGS, 1); expect(enrichFlags).toHaveBeenCalledWith(DEFAULT_CLI_FLAGS, 2); @@ -101,7 +105,7 @@ describe('createRunnerFunction', () => { it('should call getConfig with given configPath', async () => { await createRunnerFunction(['https://localhost:8080'], { configPath: 'lh-config.js', - } as LighthouseCliFlags)(undefined); + } as LighthouseCliFlags)(args); expect(getConfig).toHaveBeenCalledWith( expect.objectContaining({ configPath: 'lh-config.js' }), ); @@ -109,7 +113,7 @@ describe('createRunnerFunction', () => { it('should throw if lighthouse returns an empty result', async () => { const runner = createRunnerFunction(['fail']); - await expect(runner(undefined)).rejects.toThrow( + await expect(runner(args)).rejects.toThrow( 'Lighthouse did not produce a result.', ); }); @@ -119,7 +123,7 @@ describe('createRunnerFunction', () => { 'https://localhost:8080', 'https://localhost:8081', ]); - await expect(runner(undefined)).resolves.toEqual( + await expect(runner(args)).resolves.toEqual( expect.arrayContaining([ { slug: 'cumulative-layout-shift-1', @@ -158,7 +162,7 @@ describe('createRunnerFunction', () => { it('should handle single URL without adding index to audit slugs', async () => { const runner = createRunnerFunction(['https://localhost:8080']); - await expect(runner(undefined)).resolves.toEqual( + await expect(runner(args)).resolves.toEqual( expect.arrayContaining([ { slug: 'cumulative-layout-shift', @@ -177,7 +181,7 @@ describe('createRunnerFunction', () => { 'https://localhost:8082', ]); - await expect(runner(undefined)).resolves.toEqual( + await expect(runner(args)).resolves.toEqual( expect.arrayContaining([ { slug: 'cumulative-layout-shift-1', @@ -194,15 +198,14 @@ describe('createRunnerFunction', () => { ]), ); - expect(ui()).toHaveLogged( - 'warn', - `Lighthouse did not produce a result for URL: fail`, + expect(logger.warn).toHaveBeenCalledWith( + `Lighthouse did not produce a result for URL: ${ansis.underline.blueBright('fail')}`, ); }); it('should throw error if all URLs fail in multiple URL scenario', async () => { const runner = createRunnerFunction(['fail1', 'fail2', 'fail3']); - await expect(runner(undefined)).rejects.toThrow( + await expect(runner(args)).rejects.toThrow( 'Lighthouse failed to produce results for all URLs.', ); }); @@ -213,7 +216,7 @@ describe('createRunnerFunction', () => { 'https://localhost:8081', ]); - await runner(undefined); + await runner(args); expect(runLighthouse).toHaveBeenCalledWith( 'https://localhost:8080', diff --git a/packages/plugin-lighthouse/src/lib/runner/utils.ts b/packages/plugin-lighthouse/src/lib/runner/utils.ts index 376bfffba..37881eea5 100644 --- a/packages/plugin-lighthouse/src/lib/runner/utils.ts +++ b/packages/plugin-lighthouse/src/lib/runner/utils.ts @@ -1,4 +1,4 @@ -import { bold } from 'ansis'; +import ansis from 'ansis'; import type { Config, FormattedIcu } from 'lighthouse'; import log from 'lighthouse-logger'; import desktopConfig from 'lighthouse/core/config/desktop-config.js'; @@ -12,10 +12,10 @@ import type { AuditOutput, AuditOutputs } from '@code-pushup/models'; import { formatReportScore, importModule, + logger, pluginWorkDir, readJsonFile, stringifyError, - ui, } from '@code-pushup/utils'; import { LIGHTHOUSE_PLUGIN_SLUG } from '../constants.js'; import type { LighthouseOptions } from '../types.js'; @@ -33,7 +33,7 @@ export function normalizeAuditOutputs( export class LighthouseAuditParsingError extends Error { constructor(slug: string, error: unknown) { super( - `\nAudit ${bold(slug)} failed parsing details: \n${stringifyError(error)}`, + `\nAudit ${ansis.bold(slug)} failed parsing details: \n${stringifyError(error)}`, ); } } @@ -136,7 +136,7 @@ export async function getConfig( } else if (/\.(ts|js|mjs)$/.test(filepath)) { return importModule({ filepath, format: 'esm' }); } else { - ui().logger.info(`Format of file ${filepath} not supported`); + logger.warn(`Format of file ${filepath} not supported`); } } else if (preset != null) { switch (preset) { @@ -149,7 +149,7 @@ export async function getConfig( default: // as preset is a string literal the default case here is normally caught by TS and not possible to happen. Now in reality it can happen and preset could be a string not included in the literal. // Therefore, we have to use `as string`. Otherwise, it will consider preset as type never - ui().logger.info(`Preset "${preset as string}" is not supported`); + logger.warn(`Preset "${preset as string}" is not supported`); } } return undefined; diff --git a/packages/plugin-lighthouse/src/lib/runner/utils.unit.test.ts b/packages/plugin-lighthouse/src/lib/runner/utils.unit.test.ts index 17744d48b..a3d0df090 100644 --- a/packages/plugin-lighthouse/src/lib/runner/utils.unit.test.ts +++ b/packages/plugin-lighthouse/src/lib/runner/utils.unit.test.ts @@ -13,7 +13,7 @@ import { auditOutputsSchema, } from '@code-pushup/models'; import { MEMFS_VOLUME } from '@code-pushup/test-utils'; -import { ui } from '@code-pushup/utils'; +import { logger, ui } from '@code-pushup/utils'; import { DEFAULT_CLI_FLAGS } from './constants.js'; import { unsupportedDetailTypes } from './details/details.js'; import type { LighthouseCliFlags } from './types.js'; @@ -265,7 +265,7 @@ describe('toAuditOutputs', () => { ), { verbose: true }, ); - expect(ui()).toHaveLoggedTimes(1); + expect(logger.warn).toHaveBeenCalledTimes(1); }); it('should not parse empty audit details', () => { @@ -348,7 +348,7 @@ describe('getConfig', () => { await expect( getConfig({ preset: 'wrong' as 'desktop' }), ).resolves.toBeUndefined(); - expect(ui()).toHaveLogged('info', 'Preset "wrong" is not supported'); + expect(logger.warn).toHaveBeenCalledWith('Preset "wrong" is not supported'); }); it('should load config from json file if configPath is specified', async () => { @@ -381,7 +381,9 @@ describe('getConfig', () => { await expect( getConfig({ configPath: path.join('wrong.not') }), ).resolves.toBeUndefined(); - expect(ui()).toHaveLogged('info', 'Format of file wrong.not not supported'); + expect(logger.warn).toHaveBeenCalledWith( + 'Format of file wrong.not not supported', + ); }); }); From b70047798c3e886b96b9f9e4d8164c857c54cff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Chalk?= Date: Fri, 7 Nov 2025 17:34:39 +0100 Subject: [PATCH 4/4] feat(plugin-typescript): replace @poppinss/cliui with new logger --- packages/plugin-typescript/src/lib/utils.ts | 4 ++-- packages/plugin-typescript/src/lib/utils.unit.test.ts | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/plugin-typescript/src/lib/utils.ts b/packages/plugin-typescript/src/lib/utils.ts index 6143489e4..e4eee7837 100644 --- a/packages/plugin-typescript/src/lib/utils.ts +++ b/packages/plugin-typescript/src/lib/utils.ts @@ -1,6 +1,6 @@ import type { CompilerOptions } from 'typescript'; import type { Audit, CategoryConfig, CategoryRef } from '@code-pushup/models'; -import { kebabCaseToCamelCase, ui } from '@code-pushup/utils'; +import { kebabCaseToCamelCase, logger } from '@code-pushup/utils'; import { AUDITS, GROUPS, TYPESCRIPT_PLUGIN_SLUG } from './constants.js'; import type { TypescriptPluginConfig, @@ -146,6 +146,6 @@ export function logSkippedAudits(audits: Audit[]) { audit => !audits.some(filtered => filtered.slug === audit.slug), ).map(audit => kebabCaseToCamelCase(audit.slug)); if (skippedAudits.length > 0) { - ui().logger.info(`Skipped audits: [${skippedAudits.join(', ')}]`); + logger.info(`Skipped audits: [${skippedAudits.join(', ')}]`); } } diff --git a/packages/plugin-typescript/src/lib/utils.unit.test.ts b/packages/plugin-typescript/src/lib/utils.unit.test.ts index 274ca794c..90c65026c 100644 --- a/packages/plugin-typescript/src/lib/utils.unit.test.ts +++ b/packages/plugin-typescript/src/lib/utils.unit.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { type Audit, categoryRefSchema } from '@code-pushup/models'; -import { ui } from '@code-pushup/utils'; +import { logger } from '@code-pushup/utils'; import { AUDITS } from './constants.js'; import { filterAuditsByCompilerOptions, @@ -124,8 +124,7 @@ describe('logSkippedAudits', () => { it('should warn about skipped audits', () => { logSkippedAudits(AUDITS.slice(0, -1)); - expect(ui()).toHaveLogged( - 'info', + expect(logger.info).toHaveBeenCalledWith( expect.stringContaining(`Skipped audits: [`), ); }); @@ -133,6 +132,8 @@ describe('logSkippedAudits', () => { it('should camel case the slugs in the audit message', () => { logSkippedAudits(AUDITS.slice(0, -1)); - expect(ui()).toHaveLogged('info', expect.stringContaining(`unknownCodes`)); + expect(logger.info).toHaveBeenCalledWith( + expect.stringContaining(`unknownCodes`), + ); }); });