diff --git a/nx.json b/nx.json index 959979ed1..7b41eba43 100644 --- a/nx.json +++ b/nx.json @@ -28,6 +28,9 @@ ], "test-vitest-inputs": [ "os", + { + "env": "NX_VERBOSE_LOGGING" + }, { "externalDependencies": ["vitest"] } diff --git a/packages/ci/README.md b/packages/ci/README.md index cbd15e6f7..23d277d05 100644 --- a/packages/ci/README.md +++ b/packages/ci/README.md @@ -106,7 +106,6 @@ Optionally, you can override default options for further customization: | `silent` | `boolean` | `false` | Hides logs from CLI commands (errors will be printed) | | `bin` | `string` | `'npx --no-install code-pushup'` | Command for executing Code PushUp CLI | | `detectNewIssues` | `boolean` | `true` | Toggles if new issues should be detected and returned in `newIssues` property | -| `logger` | `Logger` | `console` | Logger for reporting progress and encountered problems | | `skipComment` | `boolean` | `false` | Toggles if comparison comment is posted to PR | | `configPatterns` | `ConfigPatterns \| null` | `null` | Additional configuration which enables [faster CI runs](#faster-ci-runs-with-configpatterns) | | `searchCommits` | `boolean \| number` | `false` | If base branch has no cached report in portal, [extends search up to 100 recent commits](#search-latest-commits-for-previous-report) | @@ -115,15 +114,6 @@ Optionally, you can override default options for further customization: [^2]: The `{task}` pattern is replaced with the `task` value, so the default behaviour is to list projects using `npx nx show projects --with-target=code-pushup --json`. The `nxProjectsFilter` options gives Nx users the flexibility to filter projects in alternative ways supported by the Nx CLI (e.g. `--affected`, `--projects`, `--exclude`, `--type`) - refer to [options in Nx docs](https://nx.dev/nx-api/nx/documents/show#options) for details. -The `Logger` object has the following required properties: - -| Property | Type | Description | -| :------- | :-------------------------- | :----------------- | -| `error` | `(message: string) => void` | Prints error log | -| `warn` | `(message: string) => void` | Prints warning log | -| `info` | `(message: string) => void` | Prints info log | -| `debug` | `(message: string) => void` | Prints debug log | - ## Standalone mode By default, it is assumed that Code PushUp is set up to run on the whole repo with one command (_standalone mode_). diff --git a/packages/ci/src/lib/cli/commands/collect.ts b/packages/ci/src/lib/cli/commands/collect.ts index 9423c4202..3428e91dc 100644 --- a/packages/ci/src/lib/cli/commands/collect.ts +++ b/packages/ci/src/lib/cli/commands/collect.ts @@ -1,5 +1,5 @@ import { DEFAULT_PERSIST_FORMAT } from '@code-pushup/models'; -import { executeProcess, isVerbose } from '@code-pushup/utils'; +import { executeProcess } from '@code-pushup/utils'; import type { CommandContext } from '../context.js'; export async function runCollect( @@ -9,7 +9,6 @@ export async function runCollect( await executeProcess({ command: bin, args: [ - ...(isVerbose() ? ['--verbose'] : []), ...(config ? [`--config=${config}`] : []), ...(hasFormats ? [] diff --git a/packages/ci/src/lib/cli/commands/compare.ts b/packages/ci/src/lib/cli/commands/compare.ts index 6ce6d0726..641b9c300 100644 --- a/packages/ci/src/lib/cli/commands/compare.ts +++ b/packages/ci/src/lib/cli/commands/compare.ts @@ -1,5 +1,5 @@ import { DEFAULT_PERSIST_FORMAT } from '@code-pushup/models'; -import { executeProcess, isVerbose } from '@code-pushup/utils'; +import { executeProcess } from '@code-pushup/utils'; import type { CommandContext } from '../context.js'; export async function runCompare( @@ -10,7 +10,6 @@ export async function runCompare( command: bin, args: [ 'compare', - ...(isVerbose() ? ['--verbose'] : []), ...(config ? [`--config=${config}`] : []), ...(hasFormats ? [] diff --git a/packages/ci/src/lib/cli/commands/merge-diffs.ts b/packages/ci/src/lib/cli/commands/merge-diffs.ts index 347bfc191..8f24208cb 100644 --- a/packages/ci/src/lib/cli/commands/merge-diffs.ts +++ b/packages/ci/src/lib/cli/commands/merge-diffs.ts @@ -3,7 +3,7 @@ import { DEFAULT_PERSIST_FILENAME, DEFAULT_PERSIST_OUTPUT_DIR, } from '@code-pushup/models'; -import { executeProcess, isVerbose } from '@code-pushup/utils'; +import { executeProcess } from '@code-pushup/utils'; import type { CommandContext } from '../context.js'; export async function runMergeDiffs( @@ -17,7 +17,6 @@ export async function runMergeDiffs( command: bin, args: [ 'merge-diffs', - ...(isVerbose() ? ['--verbose'] : []), ...files.map(file => `--files=${file}`), ...(config ? [`--config=${config}`] : []), `--persist.outputDir=${outputDir}`, diff --git a/packages/ci/src/lib/cli/commands/print-config.ts b/packages/ci/src/lib/cli/commands/print-config.ts index 3d8512428..6d740ff6f 100644 --- a/packages/ci/src/lib/cli/commands/print-config.ts +++ b/packages/ci/src/lib/cli/commands/print-config.ts @@ -2,7 +2,6 @@ import { rm } from 'node:fs/promises'; import path from 'node:path'; import { executeProcess, - isVerbose, readJsonFile, stringifyError, } from '@code-pushup/utils'; @@ -31,7 +30,6 @@ export async function runPrintConfig({ args: [ ...(config ? [`--config=${config}`] : []), 'print-config', - ...(isVerbose() ? ['--verbose'] : []), `--output=${outputPath}`, ], cwd: directory, diff --git a/packages/ci/src/lib/comment.ts b/packages/ci/src/lib/comment.ts index 51b9df777..3f1b7e377 100644 --- a/packages/ci/src/lib/comment.ts +++ b/packages/ci/src/lib/comment.ts @@ -1,17 +1,16 @@ import { readFile } from 'node:fs/promises'; -import type { Logger, ProviderAPIClient } from './models.js'; +import { logger } from '@code-pushup/utils'; +import type { ProviderAPIClient } from './models.js'; export async function commentOnPR( mdPath: string, api: ProviderAPIClient, - logger: Logger, ): Promise { const markdown = await readFile(mdPath, 'utf8'); const identifier = ``; const body = truncateBody( `${markdown}\n\n${identifier}\n`, api.maxCommentChars, - logger, ); const comments = await api.listComments(); @@ -37,7 +36,7 @@ export async function commentOnPR( return createdComment.id; } -function truncateBody(body: string, max: number, logger: Logger): string { +function truncateBody(body: string, max: number): string { const truncateWarning = '...*[Comment body truncated]*'; if (body.length > max) { logger.warn(`Comment body is too long. Truncating to ${max} characters.`); diff --git a/packages/ci/src/lib/comment.unit.test.ts b/packages/ci/src/lib/comment.unit.test.ts index 97a0c4c16..95022c7e0 100644 --- a/packages/ci/src/lib/comment.unit.test.ts +++ b/packages/ci/src/lib/comment.unit.test.ts @@ -2,8 +2,9 @@ import { vol } from 'memfs'; import { writeFile } from 'node:fs/promises'; import path from 'node:path'; import { MEMFS_VOLUME } from '@code-pushup/test-utils'; +import { logger } from '@code-pushup/utils'; import { commentOnPR } from './comment.js'; -import type { Comment, Logger, ProviderAPIClient } from './models.js'; +import type { Comment, ProviderAPIClient } from './models.js'; describe('commentOnPR', () => { const diffText = '# Code PushUp\n\nNo changes to report.\n'; @@ -28,13 +29,6 @@ describe('commentOnPR', () => { listComments: vi.fn(), } satisfies ProviderAPIClient; - const logger: Logger = { - error: vi.fn(), - warn: vi.fn(), - info: vi.fn(), - debug: vi.fn(), - }; - beforeEach(() => { vol.fromJSON({ [diffFile]: diffText }, MEMFS_VOLUME); api.listComments.mockResolvedValue([]); @@ -43,7 +37,7 @@ describe('commentOnPR', () => { it('should create new comment if none existing', async () => { api.listComments.mockResolvedValue([]); - await expect(commentOnPR(diffPath, api, logger)).resolves.toBe(comment.id); + await expect(commentOnPR(diffPath, api)).resolves.toBe(comment.id); expect(api.listComments).toHaveBeenCalled(); expect(api.createComment).toHaveBeenCalledWith(comment.body); @@ -53,7 +47,7 @@ describe('commentOnPR', () => { it("should create new comment if existing comments don't match", async () => { api.listComments.mockResolvedValue([otherComment]); - await expect(commentOnPR(diffPath, api, logger)).resolves.toBe(comment.id); + await expect(commentOnPR(diffPath, api)).resolves.toBe(comment.id); expect(api.listComments).toHaveBeenCalled(); expect(api.createComment).toHaveBeenCalledWith(comment.body); @@ -63,7 +57,7 @@ describe('commentOnPR', () => { it('should update previous comment if it matches', async () => { api.listComments.mockResolvedValue([comment]); - await expect(commentOnPR(diffPath, api, logger)).resolves.toBe(comment.id); + await expect(commentOnPR(diffPath, api)).resolves.toBe(comment.id); expect(api.listComments).toHaveBeenCalled(); expect(api.createComment).not.toHaveBeenCalled(); @@ -73,7 +67,7 @@ describe('commentOnPR', () => { it('should update previous comment which matches and ignore other comments', async () => { api.listComments.mockResolvedValue([otherComment, comment]); - await expect(commentOnPR(diffPath, api, logger)).resolves.toBe(comment.id); + await expect(commentOnPR(diffPath, api)).resolves.toBe(comment.id); expect(api.listComments).toHaveBeenCalled(); expect(api.createComment).not.toHaveBeenCalled(); @@ -86,7 +80,7 @@ describe('commentOnPR', () => { .join('\n'); await writeFile(diffPath, longDiffText); - await expect(commentOnPR(diffPath, api, logger)).resolves.toBe(comment.id); + await expect(commentOnPR(diffPath, api)).resolves.toBe(comment.id); expect(api.createComment).toHaveBeenCalledWith( expect.stringContaining('...*[Comment body truncated]*'), diff --git a/packages/ci/src/lib/create-execution-observer.ts b/packages/ci/src/lib/create-execution-observer.ts index bb4c0a261..9d555077c 100644 --- a/packages/ci/src/lib/create-execution-observer.ts +++ b/packages/ci/src/lib/create-execution-observer.ts @@ -1,4 +1,4 @@ -import { type ProcessObserver, isVerbose } from '@code-pushup/utils'; +import { type ProcessObserver, logger } from '@code-pushup/utils'; export function createExecutionObserver( { @@ -9,11 +9,11 @@ export function createExecutionObserver( ): ProcessObserver { return { onStderr: stderr => { - console.warn(stderr); + logger.warn(stderr); }, - ...((!silent || isVerbose()) && { + ...((!silent || logger.isVerbose()) && { onStdout: stdout => { - console.info(stdout); + logger.info(stdout); }, }), }; diff --git a/packages/ci/src/lib/models.ts b/packages/ci/src/lib/models.ts index 79ddd6671..784fcc204 100644 --- a/packages/ci/src/lib/models.ts +++ b/packages/ci/src/lib/models.ts @@ -18,7 +18,6 @@ export type Options = { silent?: boolean; debug?: boolean; detectNewIssues?: boolean; - logger?: Logger; skipComment?: boolean; configPatterns?: ConfigPatterns | null; searchCommits?: boolean | number; @@ -66,16 +65,6 @@ export type GitBranch = { sha: string; }; -/** - * Logger instance (e.g. `console`) for reporting progress and problems - */ -export type Logger = { - error: (message: string) => void; - warn: (message: string) => void; - info: (message: string) => void; - debug: (message: string) => void; -}; - /** * Code PushUp config patterns which hold for every project in monorepo. * Providing this information upfront makes CI runs faster (skips print-config). diff --git a/packages/ci/src/lib/monorepo/list-projects.ts b/packages/ci/src/lib/monorepo/list-projects.ts index 4868fdac0..c127daa89 100644 --- a/packages/ci/src/lib/monorepo/list-projects.ts +++ b/packages/ci/src/lib/monorepo/list-projects.ts @@ -1,7 +1,8 @@ import { glob } from 'glob'; import path from 'node:path'; +import { logger } from '@code-pushup/utils'; import { createExecutionObserver } from '../create-execution-observer.js'; -import type { Logger, Settings } from '../models.js'; +import type { Settings } from '../models.js'; import { detectMonorepoTool } from './detect-tool.js'; import { getToolHandler } from './handlers/index.js'; import { listPackages } from './packages.js'; @@ -24,7 +25,6 @@ export type RunManyCommand = ( export async function listMonorepoProjects( settings: Settings, ): Promise { - const logger = settings.logger; const options = createMonorepoHandlerOptions(settings); const tool = await resolveMonorepoTool(settings, options); @@ -50,7 +50,6 @@ export async function listMonorepoProjects( patterns: settings.projects, cwd: options.cwd, bin: settings.bin, - logger, }); return { tool, projects }; } @@ -58,7 +57,6 @@ export async function listMonorepoProjects( const projects = await listProjectsByNpmPackages({ cwd: options.cwd, bin: settings.bin, - logger, }); return { tool, projects }; } @@ -71,7 +69,6 @@ async function resolveMonorepoTool( // shouldn't happen, handled by caller throw new Error('Monorepo mode not enabled'); } - const logger = settings.logger; if (typeof settings.monorepo === 'string') { logger.info(`Using monorepo tool "${settings.monorepo}" from inputs`); @@ -108,9 +105,8 @@ async function listProjectsByGlobs(args: { patterns: string[]; cwd: string; bin: string; - logger: Logger; }): Promise { - const { patterns, cwd, bin, logger } = args; + const { patterns, cwd, bin } = args; const directories = await glob( patterns.map(pattern => pattern.replace(/\/$/, '/')), @@ -134,9 +130,8 @@ async function listProjectsByGlobs(args: { async function listProjectsByNpmPackages(args: { cwd: string; bin: string; - logger: Logger; }): Promise { - const { cwd, bin, logger } = args; + const { cwd, bin } = args; const packages = await listPackages(cwd); diff --git a/packages/ci/src/lib/monorepo/list-projects.unit.test.ts b/packages/ci/src/lib/monorepo/list-projects.unit.test.ts index 5476737fe..8518e8f04 100644 --- a/packages/ci/src/lib/monorepo/list-projects.unit.test.ts +++ b/packages/ci/src/lib/monorepo/list-projects.unit.test.ts @@ -19,12 +19,6 @@ describe('listMonorepoProjects', () => { nxProjectsFilter: '--with-target={task}', directory: MEMFS_VOLUME, bin: 'npx --no-install code-pushup', - logger: { - error: vi.fn(), - warn: vi.fn(), - info: vi.fn(), - debug: vi.fn(), - }, }; const pkgJsonContent = (content: PackageJson): string => diff --git a/packages/ci/src/lib/output-files.ts b/packages/ci/src/lib/output-files.ts index 669e86e26..f77710fb4 100644 --- a/packages/ci/src/lib/output-files.ts +++ b/packages/ci/src/lib/output-files.ts @@ -1,7 +1,7 @@ import { copyFile, mkdir } from 'node:fs/promises'; import path from 'node:path'; import { DEFAULT_PERSIST_FILENAME, type Format } from '@code-pushup/models'; -import { objectFromEntries, objectToKeys } from '@code-pushup/utils'; +import { logger, objectFromEntries, objectToKeys } from '@code-pushup/utils'; import type { OutputFiles, Settings } from './models.js'; import type { ProjectConfig } from './monorepo/tools.js'; @@ -13,12 +13,12 @@ export async function saveOutputFiles>({ project, type, files, - settings: { logger, directory }, + settings: { directory }, }: { project: Pick | null; type: OutputType; files: T; - settings: Pick; + settings: Pick; }): Promise { const baseDir = project ? path.join(BASE_DIR, project.name) : BASE_DIR; const outputDir = path.join(directory, baseDir, `.${type}`); diff --git a/packages/ci/src/lib/output-files.unit.test.ts b/packages/ci/src/lib/output-files.unit.test.ts index 280733e11..d00c725d1 100644 --- a/packages/ci/src/lib/output-files.unit.test.ts +++ b/packages/ci/src/lib/output-files.unit.test.ts @@ -2,18 +2,16 @@ import { vol } from 'memfs'; import { readFile } from 'node:fs/promises'; import path from 'node:path'; import { MEMFS_VOLUME } from '@code-pushup/test-utils'; +import { logger } from '@code-pushup/utils'; import type { Settings } from './models.js'; import { saveOutputFiles } from './output-files.js'; describe('saveOutputFiles', () => { - const settings: Pick = { - logger: console, + const settings: Pick = { directory: MEMFS_VOLUME, }; beforeEach(() => { - vi.spyOn(settings.logger, 'debug').mockImplementation(() => {}); - vol.fromJSON( { 'report.json': '{ "score": 1 }', @@ -121,10 +119,10 @@ describe('saveOutputFiles', () => { settings, }); - expect(settings.logger.debug).toHaveBeenCalledWith( + expect(logger.debug).toHaveBeenCalledWith( `Copied current report from ${path.join(MEMFS_VOLUME, 'report.json')} to ${path.join(MEMFS_VOLUME, '.code-pushup/.ci/.current/report.json')}`, ); - expect(settings.logger.debug).toHaveBeenCalledWith( + expect(logger.debug).toHaveBeenCalledWith( `Copied current report from ${path.join(MEMFS_VOLUME, 'report.md')} to ${path.join(MEMFS_VOLUME, '.code-pushup/.ci/.current/report.md')}`, ); }); diff --git a/packages/ci/src/lib/run-monorepo.ts b/packages/ci/src/lib/run-monorepo.ts index f913115eb..de0c53fa7 100644 --- a/packages/ci/src/lib/run-monorepo.ts +++ b/packages/ci/src/lib/run-monorepo.ts @@ -4,6 +4,7 @@ import { type ExcludeNullableProps, asyncSequential, hasNoNullableProps, + logger, } from '@code-pushup/utils'; import { type CommandContext, @@ -50,7 +51,6 @@ export async function runInMonorepoMode( env: RunEnv, ): Promise { const { api, settings } = env; - const { logger } = settings; logger.info('Running Code PushUp in monorepo mode'); @@ -81,7 +81,7 @@ export async function runInMonorepoMode( const commentId = settings.skipComment ? null - : await commentOnPR(diffPath, api, logger); + : await commentOnPR(diffPath, api); return { mode: 'monorepo', @@ -105,9 +105,7 @@ function runProjectsIndividually( projects: ProjectConfig[], env: RunEnv, ): Promise { - env.settings.logger.info( - `Running on ${projects.length} projects individually`, - ); + logger.info(`Running on ${projects.length} projects individually`); return asyncSequential(projects, project => runOnProject(project, env)); } @@ -120,7 +118,6 @@ async function runProjectsInBulk( refs: { base }, settings, } = env; - const { logger } = settings; logger.info( `Running on ${projects.length} projects in bulk (parallel: ${settings.parallel})`, @@ -158,7 +155,7 @@ async function loadProjectEnvs( projectEnvs: ProjectEnv[]; hasFormats: boolean; }> { - const { logger, configPatterns } = settings; + const { configPatterns } = settings; const projectEnvs: ProjectEnv[] = configPatterns ? projects.map( @@ -214,7 +211,7 @@ async function compareProjectsInBulk( const uncachedProjectReports = projectReportsWithCache.filter( ({ prevReport }) => !prevReport, ); - env.settings.logger.info( + logger.info( `${currProjectReports.length - uncachedProjectReports.length} out of ${currProjectReports.length} projects loaded previous report from artifact cache`, ); @@ -292,7 +289,7 @@ async function collectPreviousReports( env: RunEnv, ): Promise>> { const { settings } = env; - const { logger, configPatterns } = settings; + const { configPatterns } = settings; if (uncachedProjectReports.length === 0) { return {}; @@ -380,9 +377,7 @@ async function collectMany( const countText = onlyProjects ? `${onlyProjects.length} previous` : 'all current'; - settings.logger.debug( - `Collected ${countText} reports using command \`${command}\``, - ); + logger.debug(`Collected ${countText} reports using command \`${command}\``); } async function compareMany( @@ -402,7 +397,7 @@ async function compareMany( await runCompare(ctx, { hasFormats }); - settings.logger.debug('Compared all project reports'); + logger.debug('Compared all project reports'); } export function allProjectsHaveDefaultPersistFormats( diff --git a/packages/ci/src/lib/run-standalone.ts b/packages/ci/src/lib/run-standalone.ts index 303db7de5..cca6c5a50 100644 --- a/packages/ci/src/lib/run-standalone.ts +++ b/packages/ci/src/lib/run-standalone.ts @@ -1,3 +1,4 @@ +import { logger } from '@code-pushup/utils'; import { commentOnPR } from './comment.js'; import type { StandaloneRunResult } from './models.js'; import { type RunEnv, runOnProject } from './run-utils.js'; @@ -6,7 +7,6 @@ export async function runInStandaloneMode( env: RunEnv, ): Promise { const { api, settings } = env; - const { logger } = settings; logger.info('Running Code PushUp in standalone project mode'); @@ -14,7 +14,7 @@ export async function runInStandaloneMode( const commentMdPath = files.comparison?.md; if (!settings.skipComment && commentMdPath) { - const commentId = await commentOnPR(commentMdPath, api, logger); + const commentId = await commentOnPR(commentMdPath, api); return { mode: 'standalone', files, diff --git a/packages/ci/src/lib/run-utils.ts b/packages/ci/src/lib/run-utils.ts index dd92a6c21..609cf89f0 100644 --- a/packages/ci/src/lib/run-utils.ts +++ b/packages/ci/src/lib/run-utils.ts @@ -13,6 +13,7 @@ import { type Diff, createReportPath, interpolate, + logger, objectFromEntries, readJsonFile, removeUndefinedAndEmptyProps, @@ -34,7 +35,6 @@ import type { ConfigPatterns, GitBranch, GitRefs, - Logger, Options, ProjectRunResult, ProviderAPIClient, @@ -90,10 +90,7 @@ export async function createRunEnv( options: Options | undefined, git: SimpleGit, ): Promise { - const inferredVerbose: boolean = - options?.debug === true || options?.silent === false; - // eslint-disable-next-line functional/immutable-data - process.env['CP_VERBOSE'] = `${inferredVerbose}`; + logger.setVerbose(options?.debug === true || options?.silent === false); const [head, base] = await Promise.all([ normalizeGitRef(refs.head, git), @@ -112,17 +109,14 @@ export async function createRunEnv( } function sanitizeOptions(options: Options): Options { - const logger = options.logger ?? DEFAULT_SETTINGS.logger; - return removeUndefinedAndEmptyProps({ ...options, - searchCommits: sanitizeSearchCommits(options.searchCommits, logger), + searchCommits: sanitizeSearchCommits(options.searchCommits), }); } function sanitizeSearchCommits( searchCommits: Options['searchCommits'], - logger: Logger, ): Options['searchCommits'] { if ( typeof searchCommits === 'number' && @@ -147,7 +141,6 @@ export async function runOnProject( refs: { head, base }, settings, } = env; - const logger = settings.logger; const ctx = createCommandContext(settings, project); @@ -201,7 +194,6 @@ export async function compareReports( ): Promise { const { ctx, env, config } = args; const { settings } = env; - const { logger } = settings; await prepareReportFilesToCompare(args); await runCompare(ctx, { hasFormats: hasDefaultPersistFormats(config) }); @@ -218,13 +210,12 @@ export async function compareReports( export async function prepareReportFilesToCompare( args: CompareReportsArgs, ): Promise> { - const { config, project, env, ctx } = args; + const { config, project, ctx } = args; const { outputDir = DEFAULT_PERSIST_OUTPUT_DIR, filename = DEFAULT_PERSIST_FILENAME, } = config.persist ?? {}; const label = project?.name; - const { logger } = env.settings; const originalReports = await Promise.all( [args.currReport, args.prevReport].map(({ files }) => @@ -320,7 +311,7 @@ export async function collectPreviousReport( ): Promise | null> { const { ctx, env, base, project } = args; const { settings } = env; - const { logger, configPatterns } = settings; + const { configPatterns } = settings; const cachedBaseReport = await loadCachedBaseReport(args); if (cachedBaseReport) { @@ -374,10 +365,9 @@ async function loadCachedBaseReportFromArtifacts( args: BaseReportArgs, ): Promise { const { - env: { api, settings }, + env: { api }, project, } = args; - const { logger } = settings; if (api.downloadReportArtifact == null) { return null; @@ -408,7 +398,6 @@ async function loadCachedBaseReportFromPortal( env: { settings }, base, } = args; - const { logger } = settings; if (!config.upload) { return null; @@ -450,10 +439,7 @@ export async function runInBaseBranch( env: RunEnv, fn: () => Promise, ): Promise { - const { - git, - settings: { logger }, - } = env; + const { git } = env; await git.fetch('origin', base.ref, ['--depth=1']); await git.checkout(['-f', base.sha]); @@ -470,13 +456,7 @@ export async function runInBaseBranch( export async function checkPrintConfig( args: BaseReportArgs, ): Promise { - const { - project, - ctx, - base, - env: { settings }, - } = args; - const { logger } = settings; + const { project, ctx, base } = args; const operation = project ? `Executing print-config for project ${project.name}` @@ -549,10 +529,7 @@ export async function findNewIssues( prevReport, config, ctx, - env: { - git, - settings: { logger }, - }, + env: { git }, } = args; const diffFiles = persistedFilesFromConfig(config, { diff --git a/packages/ci/src/lib/run.int.test.ts b/packages/ci/src/lib/run.int.test.ts index 329a6b211..f3a7cc8f9 100644 --- a/packages/ci/src/lib/run.int.test.ts +++ b/packages/ci/src/lib/run.int.test.ts @@ -18,6 +18,7 @@ import { type CoreConfig, DEFAULT_PERSIST_FILENAME, DEFAULT_PERSIST_FORMAT, + DEFAULT_PERSIST_SKIP_REPORT, } from '@code-pushup/models'; import { cleanTestFolder, @@ -26,11 +27,11 @@ import { teardownTestFolder, } from '@code-pushup/test-utils'; import * as utils from '@code-pushup/utils'; +import { logger } from '@code-pushup/utils'; import type { Comment, GitBranch, GitRefs, - Logger, Options, ProviderAPIClient, RunResult, @@ -89,17 +90,9 @@ function simulateDownloadReportFromPortal() { } describe('runInCI', () => { - const logger: Logger = { - error: vi.fn(), - warn: vi.fn(), - info: vi.fn(), - debug: vi.fn(), - }; - const options = { bin: 'npx code-pushup', directory: workDir, - logger, } satisfies Options; const mockComment: Comment = { @@ -741,6 +734,7 @@ describe('runInCI', () => { outputDir: persistOutputDir, filename: DEFAULT_PERSIST_FILENAME, format: DEFAULT_PERSIST_FORMAT, + skipReports: DEFAULT_PERSIST_SKIP_REPORT, }, }, }, @@ -1011,6 +1005,7 @@ describe('runInCI', () => { outputDir: persistOutputDir, filename: DEFAULT_PERSIST_FILENAME, format: DEFAULT_PERSIST_FORMAT, + skipReports: DEFAULT_PERSIST_SKIP_REPORT, }, }, }, diff --git a/packages/ci/src/lib/settings.ts b/packages/ci/src/lib/settings.ts index 28fcea4ac..b16df6708 100644 --- a/packages/ci/src/lib/settings.ts +++ b/packages/ci/src/lib/settings.ts @@ -13,7 +13,6 @@ export const DEFAULT_SETTINGS: Settings = { silent: false, debug: false, detectNewIssues: true, - logger: console, nxProjectsFilter: '--with-target={task}', skipComment: false, configPatterns: null, diff --git a/packages/ci/vitest.int.config.ts b/packages/ci/vitest.int.config.ts index ca0ef7a4d..88ea3988c 100644 --- a/packages/ci/vitest.int.config.ts +++ b/packages/ci/vitest.int.config.ts @@ -22,7 +22,7 @@ export default defineConfig({ include: ['src/**/*.int.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], globalSetup: ['../../global-setup.ts'], setupFiles: [ - '../../testing/test-setup/src/lib/console.mock.ts', + '../../testing/test-setup/src/lib/logger.mock.ts', '../../testing/test-setup/src/lib/reset.mocks.ts', ], }, diff --git a/packages/ci/vitest.unit.config.ts b/packages/ci/vitest.unit.config.ts index d8719d784..854777d69 100644 --- a/packages/ci/vitest.unit.config.ts +++ b/packages/ci/vitest.unit.config.ts @@ -24,7 +24,7 @@ export default defineConfig({ setupFiles: [ '../../testing/test-setup/src/lib/fs.mock.ts', '../../testing/test-setup/src/lib/git.mock.ts', - '../../testing/test-setup/src/lib/console.mock.ts', + '../../testing/test-setup/src/lib/logger.mock.ts', '../../testing/test-setup/src/lib/reset.mocks.ts', '../../testing/test-setup/src/lib/extend/jest-extended.matcher.ts', ], diff --git a/packages/utils/src/lib/logging.ts b/packages/utils/src/lib/logging.ts index ca344fc05..acadcc073 100644 --- a/packages/utils/src/lib/logging.ts +++ b/packages/utils/src/lib/logging.ts @@ -3,6 +3,8 @@ import { cliui } from '@poppinss/cliui'; import { underline } from 'ansis'; import { TERMINAL_WIDTH } from './reports/constants.js'; +// TODO: remove once logger is used everywhere + // eslint-disable-next-line @typescript-eslint/no-explicit-any type ArgumentsType = T extends (...args: infer U) => any ? U : never; export type CliUiBase = ReturnType; diff --git a/testing/test-setup/src/lib/cliui.mock.ts b/testing/test-setup/src/lib/cliui.mock.ts index bed220fdb..4e55c5d8b 100644 --- a/testing/test-setup/src/lib/cliui.mock.ts +++ b/testing/test-setup/src/lib/cliui.mock.ts @@ -1,5 +1,7 @@ import { beforeAll, beforeEach, vi } from 'vitest'; +// TODO: remove once logger is used everywhere + beforeAll(async () => { const utils: typeof import('@code-pushup/utils') = await vi.importActual('@code-pushup/utils'); diff --git a/testing/test-setup/src/lib/console.mock.ts b/testing/test-setup/src/lib/console.mock.ts index 1f4ca3945..9d8cbfbcd 100644 --- a/testing/test-setup/src/lib/console.mock.ts +++ b/testing/test-setup/src/lib/console.mock.ts @@ -1,5 +1,7 @@ import { type MockInstance, afterEach, beforeEach, vi } from 'vitest'; +// TODO: remove once logger is used everywhere + let consoleInfoSpy: MockInstance | undefined; let consoleWarnSpy: MockInstance | undefined; let consoleErrorSpy: MockInstance | undefined; diff --git a/testing/test-setup/src/lib/extend/ui-logger.matcher.ts b/testing/test-setup/src/lib/extend/ui-logger.matcher.ts index e8b6e4a25..083d58743 100644 --- a/testing/test-setup/src/lib/extend/ui-logger.matcher.ts +++ b/testing/test-setup/src/lib/extend/ui-logger.matcher.ts @@ -8,6 +8,8 @@ import { hasExpectedMessage, } from './ui-logger.matcher.utils'; +// TODO: remove once logger is used everywhere + type CliUi = ReturnType; export type CustomUiLoggerMatchers = { diff --git a/testing/test-setup/src/lib/logger.mock.ts b/testing/test-setup/src/lib/logger.mock.ts new file mode 100644 index 000000000..8e2e00ad7 --- /dev/null +++ b/testing/test-setup/src/lib/logger.mock.ts @@ -0,0 +1,46 @@ +import { type MockInstance, afterAll, beforeAll, vi } from 'vitest'; +import { logger } from '@code-pushup/utils'; + +const loggerSpies: MockInstance[] = []; + +beforeAll(() => { + // TODO: use vi.mockObject after Vitest update: https://vitest.dev/api/vi.html#vi-mockobject-3-2-0 + if (process.env['NX_VERBOSE_LOGGING'] === 'true') { + // only track calls, but preserve original implementation so logs are printed + loggerSpies.push( + vi.spyOn(logger, 'error'), + vi.spyOn(logger, 'warn'), + vi.spyOn(logger, 'info'), + vi.spyOn(logger, 'debug'), + vi.spyOn(logger, 'newline'), + vi.spyOn(logger, 'group'), + vi.spyOn(logger, 'task'), + vi.spyOn(logger, 'command'), + ); + } else { + // track calls and silence logs + loggerSpies.push( + vi.spyOn(logger, 'error').mockImplementation(() => {}), + vi.spyOn(logger, 'warn').mockImplementation(() => {}), + vi.spyOn(logger, 'info').mockImplementation(() => {}), + vi.spyOn(logger, 'debug').mockImplementation(() => {}), + vi.spyOn(logger, 'newline').mockImplementation(() => {}), + // make sure worker still gets executed + vi.spyOn(logger, 'group').mockImplementation(async (_, worker) => { + await worker(); + }), + vi.spyOn(logger, 'task').mockImplementation(async (_, worker) => { + await worker(); + }), + vi.spyOn(logger, 'command').mockImplementation(async (_, worker) => { + await worker(); + }), + ); + } +}); + +afterAll(() => { + loggerSpies.forEach(loggerSpy => { + loggerSpy.mockRestore(); + }); +});