From 7f473ec0b08ebf4bc18e623d52ca1611c3a29987 Mon Sep 17 00:00:00 2001 From: John Doe Date: Mon, 2 Feb 2026 20:58:26 +0100 Subject: [PATCH 1/9] fix: avoid bundle require mocking in tests --- .../core-config.middleware.int.test.ts | 8 +- .../src/lib/nx/coverage-paths.ts | 1 - .../src/lib/nx/coverage-paths.unit.test.ts | 132 +++++++++--------- .../plugin-lighthouse/src/lib/runner/utils.ts | 1 - .../src/lib/runner/utils.unit.test.ts | 25 ++-- packages/utils/src/index.ts | 2 +- packages/utils/src/lib/file-system.ts | 19 --- ....int.test.ts => import-module.int.test.ts} | 2 +- packages/utils/src/lib/import-module.ts | 20 +++ 9 files changed, 106 insertions(+), 104 deletions(-) rename packages/utils/src/lib/{file-system.int.test.ts => import-module.int.test.ts} (97%) create mode 100644 packages/utils/src/lib/import-module.ts diff --git a/packages/cli/src/lib/implementation/core-config.middleware.int.test.ts b/packages/cli/src/lib/implementation/core-config.middleware.int.test.ts index 4aeba1175..a918c35c3 100644 --- a/packages/cli/src/lib/implementation/core-config.middleware.int.test.ts +++ b/packages/cli/src/lib/implementation/core-config.middleware.int.test.ts @@ -8,12 +8,8 @@ const configDirPath = path.join( '..', '..', '..', - '..', - 'testing', - 'test-fixtures', - 'src', - 'lib', - 'fixtures', + 'cli', + 'mocks', 'configs', ); diff --git a/packages/plugin-coverage/src/lib/nx/coverage-paths.ts b/packages/plugin-coverage/src/lib/nx/coverage-paths.ts index 1890e5509..b95234f52 100644 --- a/packages/plugin-coverage/src/lib/nx/coverage-paths.ts +++ b/packages/plugin-coverage/src/lib/nx/coverage-paths.ts @@ -147,7 +147,6 @@ export async function getCoveragePathForVitest( const vitestConfig = await importModule({ filepath: config, - format: 'esm', }); const reportsDirectory = diff --git a/packages/plugin-coverage/src/lib/nx/coverage-paths.unit.test.ts b/packages/plugin-coverage/src/lib/nx/coverage-paths.unit.test.ts index 5bcaaa422..08e70014f 100644 --- a/packages/plugin-coverage/src/lib/nx/coverage-paths.unit.test.ts +++ b/packages/plugin-coverage/src/lib/nx/coverage-paths.unit.test.ts @@ -13,80 +13,86 @@ import { getCoveragePathsForTarget, } from './coverage-paths.js'; -vi.mock('bundle-require', () => ({ - bundleRequire: vi.fn().mockImplementation((options: { filepath: string }) => { - const VITEST_VALID: VitestCoverageConfig = { - test: { - coverage: { - reporter: ['lcov'], - reportsDirectory: path.join('coverage', 'cli'), - }, - }, - }; +vi.mock('@code-pushup/utils', async () => { + const utils = + await vi.importActual( + '@code-pushup/utils', + ); - const VITEST_NO_DIR: VitestCoverageConfig = { - test: { coverage: { reporter: ['lcov'] } }, - }; + return { + ...utils, + importModule: vi + .fn() + .mockImplementation(async (options: { filepath: string }) => { + const VITEST_VALID: VitestCoverageConfig = { + test: { + coverage: { + reporter: ['lcov'], + reportsDirectory: path.join('coverage', 'cli'), + }, + }, + }; - const VITEST_NO_LCOV: VitestCoverageConfig = { - test: { - coverage: { - reporter: ['json'], - reportsDirectory: 'coverage', - }, - }, - }; + const VITEST_NO_DIR: VitestCoverageConfig = { + test: { coverage: { reporter: ['lcov'] } }, + }; - const JEST_VALID: JestCoverageConfig = { - coverageReporters: ['lcov'], - coverageDirectory: path.join('coverage', 'core'), - }; + const VITEST_NO_LCOV: VitestCoverageConfig = { + test: { + coverage: { + reporter: ['json'], + reportsDirectory: 'coverage', + }, + }, + }; - const JEST_NO_DIR: JestCoverageConfig = { - coverageReporters: ['lcov'], - }; + const JEST_VALID: JestCoverageConfig = { + coverageReporters: ['lcov'], + coverageDirectory: path.join('coverage', 'core'), + }; - const JEST_NO_LCOV: JestCoverageConfig = { - coverageReporters: ['json'], - coverageDirectory: 'coverage', - }; + const JEST_NO_DIR: JestCoverageConfig = { + coverageReporters: ['lcov'], + }; - const JEST_PRESET: JestCoverageConfig & { preset?: string } = { - preset: '../../jest.preset.ts', - coverageDirectory: 'coverage', - }; + const JEST_NO_LCOV: JestCoverageConfig = { + coverageReporters: ['json'], + coverageDirectory: 'coverage', + }; - const wrapReturnValue = ( - value: VitestCoverageConfig | JestCoverageConfig, - ) => ({ mod: { default: value } }); + const JEST_PRESET: JestCoverageConfig & { preset?: string } = { + preset: '../../jest.preset.ts', + coverageDirectory: 'coverage', + }; - const config = options.filepath.split('.')[0]; - switch (config) { - case 'vitest-valid': - return wrapReturnValue(VITEST_VALID); - case 'vitest-no-lcov': - return wrapReturnValue(VITEST_NO_LCOV); - case 'vitest-no-dir': - return wrapReturnValue(VITEST_NO_DIR); - case 'jest-valid': - return wrapReturnValue(JEST_VALID); - case 'jest-no-lcov': - return wrapReturnValue(JEST_NO_LCOV); - case 'jest-no-dir': - return wrapReturnValue(JEST_NO_DIR); - case 'jest-preset': - return wrapReturnValue(JEST_PRESET); - default: - return wrapReturnValue({}); - } - }), -})); + const config = options.filepath.split('.')[0]; + switch (config) { + case 'vitest-valid': + return VITEST_VALID; + case 'vitest-no-lcov': + return VITEST_NO_LCOV; + case 'vitest-no-dir': + return VITEST_NO_DIR; + case 'jest-valid': + return JEST_VALID; + case 'jest-no-lcov': + return JEST_NO_LCOV; + case 'jest-no-dir': + return JEST_NO_DIR; + case 'jest-preset': + return JEST_PRESET; + default: + return {}; + } + }), + }; +}); describe('getCoveragePathForTarget', () => { beforeEach(() => { vol.fromJSON( { - // values come from bundle-require mock above + // values come from importModule mock above 'vitest-valid.config.ts': '', 'jest-valid.config.ts': '', }, @@ -162,7 +168,7 @@ describe('getCoveragePathForVitest', () => { beforeEach(() => { vol.fromJSON( { - // values come from bundle-require mock above + // values come from importModule mock above 'vitest-valid.config.unit.ts': '', 'vitest-no-dir.config.integration.ts': '', 'vitest-no-lcov.config.integration.ts': '', @@ -260,7 +266,7 @@ describe('getCoveragePathForJest', () => { beforeEach(() => { vol.fromJSON( { - // values come from bundle-require mock above + // values come from importModule mock above 'jest-preset.config.ts': '', 'jest-valid.config.unit.ts': '', 'jest-valid.config.integration.ts': '', diff --git a/packages/plugin-lighthouse/src/lib/runner/utils.ts b/packages/plugin-lighthouse/src/lib/runner/utils.ts index a68ad368e..347c16bb4 100644 --- a/packages/plugin-lighthouse/src/lib/runner/utils.ts +++ b/packages/plugin-lighthouse/src/lib/runner/utils.ts @@ -144,7 +144,6 @@ export async function getConfig( message, result: await importModule({ filepath: configPath, - format: 'esm', }), }; } 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 a5dfee309..8f05e6391 100644 --- a/packages/plugin-lighthouse/src/lib/runner/utils.unit.test.ts +++ b/packages/plugin-lighthouse/src/lib/runner/utils.unit.test.ts @@ -25,25 +25,26 @@ import { withLocalTmpDir, } from './utils.js'; -// mock bundleRequire inside importEsmModule used for fetching config -vi.mock('bundle-require', async () => { +// mock importModule from @code-pushup/utils used for fetching config +vi.mock('@code-pushup/utils', async () => { + const utils = + await vi.importActual( + '@code-pushup/utils', + ); const { CORE_CONFIG_MOCK }: Record = await vi.importActual('@code-pushup/test-utils'); return { - bundleRequire: vi + ...utils, + importModule: vi .fn() - .mockImplementation((options: { filepath: string }) => { + .mockImplementation(async (options: { filepath: string }) => { const project = options.filepath.split('.').at(-2); return { - mod: { - default: { - ...CORE_CONFIG_MOCK, - upload: { - ...CORE_CONFIG_MOCK?.upload, - project, // returns loaded file extension to check in test - }, - }, + ...CORE_CONFIG_MOCK, + upload: { + ...CORE_CONFIG_MOCK?.upload, + project, // returns loaded file extension to check in test }, }; }), diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index ce3d64ed2..c9a1f0b30 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -41,7 +41,6 @@ export { filePathToCliArg, findLineNumberInText, findNearestFile, - importModule, pluginWorkDir, projectToFilename, readJsonFile, @@ -184,3 +183,4 @@ export type { Prettify, WithRequired, } from './lib/types.js'; +export * from './lib/import-module.js'; diff --git a/packages/utils/src/lib/file-system.ts b/packages/utils/src/lib/file-system.ts index 5956dbbff..cbe72f547 100644 --- a/packages/utils/src/lib/file-system.ts +++ b/packages/utils/src/lib/file-system.ts @@ -1,9 +1,7 @@ -import { type Options, bundleRequire } from 'bundle-require'; import { mkdir, readFile, readdir, rm, stat } from 'node:fs/promises'; import path from 'node:path'; import type { Format, PersistConfig } from '@code-pushup/models'; import { logger } from './logger.js'; -import { settlePromise } from './promises.js'; export async function readTextFile(filePath: string): Promise { const buffer = await readFile(filePath); @@ -52,23 +50,6 @@ export async function removeDirectoryIfExists(dir: string) { } } -export async function importModule(options: Options): Promise { - const resolvedStats = await settlePromise(stat(options.filepath)); - if (resolvedStats.status === 'rejected') { - throw new Error(`File '${options.filepath}' does not exist`); - } - if (!resolvedStats.value.isFile()) { - throw new Error(`Expected '${options.filepath}' to be a file`); - } - - const { mod } = await bundleRequire(options); - - if (typeof mod === 'object' && 'default' in mod) { - return mod.default as T; - } - return mod as T; -} - export function createReportPath({ outputDir, filename, diff --git a/packages/utils/src/lib/file-system.int.test.ts b/packages/utils/src/lib/import-module.int.test.ts similarity index 97% rename from packages/utils/src/lib/file-system.int.test.ts rename to packages/utils/src/lib/import-module.int.test.ts index 77d16eeff..89d91aecd 100644 --- a/packages/utils/src/lib/file-system.int.test.ts +++ b/packages/utils/src/lib/import-module.int.test.ts @@ -1,5 +1,5 @@ import path from 'node:path'; -import { importModule } from './file-system.js'; +import { importModule } from './import-module'; describe('importModule', () => { const mockDir = path.join( diff --git a/packages/utils/src/lib/import-module.ts b/packages/utils/src/lib/import-module.ts new file mode 100644 index 000000000..eaefb187b --- /dev/null +++ b/packages/utils/src/lib/import-module.ts @@ -0,0 +1,20 @@ +import { type Options, bundleRequire } from 'bundle-require'; +import { stat } from 'node:fs/promises'; +import { settlePromise } from './promises.js'; + +export async function importModule(options: Options): Promise { + const resolvedStats = await settlePromise(stat(options.filepath)); + if (resolvedStats.status === 'rejected') { + throw new Error(`File '${options.filepath}' does not exist`); + } + if (!resolvedStats.value.isFile()) { + throw new Error(`Expected '${options.filepath}' to be a file`); + } + + const { mod } = await bundleRequire(options); + + if (typeof mod === 'object' && 'default' in mod) { + return mod.default as T; + } + return mod as T; +} From 1b205be2df037348856e224d4025976a5ca0ec5a Mon Sep 17 00:00:00 2001 From: John Doe Date: Mon, 2 Feb 2026 20:58:52 +0100 Subject: [PATCH 2/9] fix: avoid depending on central mock files --- .../cli/mocks/configs/code-pushup.config.js | 43 +++++++++++++++ .../cli/mocks/configs/code-pushup.config.mjs | 43 +++++++++++++++ .../cli/mocks/configs/code-pushup.config.ts | 43 +++++++++++++++ .../code-pushup.needs-tsconfig.config.ts | 8 +++ packages/cli/mocks/configs/custom-plugin.ts | 24 +++++++++ packages/cli/mocks/configs/tsconfig.json | 11 ++++ .../fixtures/configs/code-pushup.config.js | 43 +++++++++++++++ .../configs/code-pushup.empty.config.js | 1 + .../configs/code-pushup.invalid.config.ts | 53 +++++++++++++++++++ .../code-pushup.needs-tsconfig.config.ts | 10 ++++ .../mocks/fixtures/configs/custom-plugin.ts | 24 +++++++++ .../core/mocks/fixtures/configs/tsconfig.json | 7 +++ .../implementation/read-rc-file.int.test.ts | 7 +-- .../src/lib/implementation/read-rc-file.ts | 1 - .../implementation/read-rc-file.unit.test.ts | 27 +++++----- 15 files changed, 325 insertions(+), 20 deletions(-) create mode 100644 packages/cli/mocks/configs/code-pushup.config.js create mode 100644 packages/cli/mocks/configs/code-pushup.config.mjs create mode 100644 packages/cli/mocks/configs/code-pushup.config.ts create mode 100644 packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts create mode 100644 packages/cli/mocks/configs/custom-plugin.ts create mode 100644 packages/cli/mocks/configs/tsconfig.json create mode 100644 packages/core/mocks/fixtures/configs/code-pushup.config.js create mode 100644 packages/core/mocks/fixtures/configs/code-pushup.empty.config.js create mode 100644 packages/core/mocks/fixtures/configs/code-pushup.invalid.config.ts create mode 100644 packages/core/mocks/fixtures/configs/code-pushup.needs-tsconfig.config.ts create mode 100644 packages/core/mocks/fixtures/configs/custom-plugin.ts create mode 100644 packages/core/mocks/fixtures/configs/tsconfig.json diff --git a/packages/cli/mocks/configs/code-pushup.config.js b/packages/cli/mocks/configs/code-pushup.config.js new file mode 100644 index 000000000..9f1661d4d --- /dev/null +++ b/packages/cli/mocks/configs/code-pushup.config.js @@ -0,0 +1,43 @@ +export default { + upload: { + organization: 'code-pushup', + project: 'cli-js', + apiKey: 'e2e-api-key', + server: 'https://e2e.com/api', + }, + categories: [ + { + slug: 'category-1', + title: 'Category 1', + refs: [ + { + type: 'audit', + plugin: 'node', + slug: 'node-version', + weight: 1, + }, + ], + }, + ], + plugins: [ + { + audits: [ + { + slug: 'node-version', + title: 'Node version', + description: 'prints node version to file', + docsUrl: 'https://nodejs.org/', + }, + ], + runner: { + command: 'node', + args: ['-v'], + outputFile: 'output.json', + }, + groups: [], + slug: 'node', + title: 'Node.js', + icon: 'javascript', + }, + ], +}; diff --git a/packages/cli/mocks/configs/code-pushup.config.mjs b/packages/cli/mocks/configs/code-pushup.config.mjs new file mode 100644 index 000000000..d7f531533 --- /dev/null +++ b/packages/cli/mocks/configs/code-pushup.config.mjs @@ -0,0 +1,43 @@ +export default { + upload: { + organization: 'code-pushup', + project: 'cli-mjs', + apiKey: 'e2e-api-key', + server: 'https://e2e.com/api', + }, + categories: [ + { + slug: 'category-1', + title: 'Category 1', + refs: [ + { + type: 'audit', + plugin: 'node', + slug: 'node-version', + weight: 1, + }, + ], + }, + ], + plugins: [ + { + audits: [ + { + slug: 'node-version', + title: 'Node version', + description: 'prints node version to file', + docsUrl: 'https://nodejs.org/', + }, + ], + runner: { + command: 'node', + args: ['-v'], + outputFile: 'output.json', + }, + groups: [], + slug: 'node', + title: 'Node.js', + icon: 'javascript', + }, + ], +}; diff --git a/packages/cli/mocks/configs/code-pushup.config.ts b/packages/cli/mocks/configs/code-pushup.config.ts new file mode 100644 index 000000000..7aa6f327e --- /dev/null +++ b/packages/cli/mocks/configs/code-pushup.config.ts @@ -0,0 +1,43 @@ +export default { + upload: { + organization: 'code-pushup', + project: 'cli-ts', + apiKey: 'e2e-api-key', + server: 'https://e2e.com/api', + }, + categories: [ + { + slug: 'category-1', + title: 'Category 1', + refs: [ + { + type: 'audit', + plugin: 'node', + slug: 'node-version', + weight: 1, + }, + ], + }, + ], + plugins: [ + { + audits: [ + { + slug: 'node-version', + title: 'Node version', + description: 'prints node version to file', + docsUrl: 'https://nodejs.org/', + }, + ], + runner: { + command: 'node', + args: ['-v'], + outputFile: 'output.json', + }, + groups: [], + slug: 'node', + title: 'Node.js', + icon: 'javascript', + }, + ], +}; diff --git a/packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts b/packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts new file mode 100644 index 000000000..76405daba --- /dev/null +++ b/packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts @@ -0,0 +1,8 @@ +// the point is to test runtime import which requires tsconfig for path aliases +import customPlugin from '@example/custom-plugin'; + +const config = { + plugins: [customPlugin], +}; + +export default config; diff --git a/packages/cli/mocks/configs/custom-plugin.ts b/packages/cli/mocks/configs/custom-plugin.ts new file mode 100644 index 000000000..6afe6bc80 --- /dev/null +++ b/packages/cli/mocks/configs/custom-plugin.ts @@ -0,0 +1,24 @@ +const customPluginConfig = { + slug: 'good-feels', + title: 'Good feels', + icon: 'javascript', + audits: [ + { + slug: 'always-perfect', + title: 'Always perfect', + }, + ], + runner: () => [ + { + slug: 'always-perfect', + score: 1, + value: 100, + displayValue: '✅ Perfect! 👌', + }, + ], +}; + +export function customPlugin() { + return customPluginConfig; +} +export default customPluginConfig; diff --git a/packages/cli/mocks/configs/tsconfig.json b/packages/cli/mocks/configs/tsconfig.json new file mode 100644 index 000000000..d0072f442 --- /dev/null +++ b/packages/cli/mocks/configs/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "allowImportingTsExtensions": true, + "moduleResolution": "node", + "paths": { + "@example/custom-plugin": ["./custom-plugin.ts"] + } + }, + "include": ["*.ts"] +} diff --git a/packages/core/mocks/fixtures/configs/code-pushup.config.js b/packages/core/mocks/fixtures/configs/code-pushup.config.js new file mode 100644 index 000000000..9f1661d4d --- /dev/null +++ b/packages/core/mocks/fixtures/configs/code-pushup.config.js @@ -0,0 +1,43 @@ +export default { + upload: { + organization: 'code-pushup', + project: 'cli-js', + apiKey: 'e2e-api-key', + server: 'https://e2e.com/api', + }, + categories: [ + { + slug: 'category-1', + title: 'Category 1', + refs: [ + { + type: 'audit', + plugin: 'node', + slug: 'node-version', + weight: 1, + }, + ], + }, + ], + plugins: [ + { + audits: [ + { + slug: 'node-version', + title: 'Node version', + description: 'prints node version to file', + docsUrl: 'https://nodejs.org/', + }, + ], + runner: { + command: 'node', + args: ['-v'], + outputFile: 'output.json', + }, + groups: [], + slug: 'node', + title: 'Node.js', + icon: 'javascript', + }, + ], +}; diff --git a/packages/core/mocks/fixtures/configs/code-pushup.empty.config.js b/packages/core/mocks/fixtures/configs/code-pushup.empty.config.js new file mode 100644 index 000000000..ff8b4c563 --- /dev/null +++ b/packages/core/mocks/fixtures/configs/code-pushup.empty.config.js @@ -0,0 +1 @@ +export default {}; diff --git a/packages/core/mocks/fixtures/configs/code-pushup.invalid.config.ts b/packages/core/mocks/fixtures/configs/code-pushup.invalid.config.ts new file mode 100644 index 000000000..7396948b9 --- /dev/null +++ b/packages/core/mocks/fixtures/configs/code-pushup.invalid.config.ts @@ -0,0 +1,53 @@ +import type { CoreConfig } from '@code-pushup/models'; + +export default { + persist: { outputDir: 'tmp' }, + upload: { + organization: 'code-pushup', + project: 'cli-ts', + apiKey: 'e2e-api-key', + server: 'https://e2e.com/api', + }, + categories: [ + { + slug: 'bug-prevention', + title: 'Bug prevention', + // due to duplicate category references, the config is invalid + refs: [ + { + type: 'audit', + plugin: 'node', + slug: 'node-version', + weight: 1, + }, + { + type: 'audit', + plugin: 'node', + slug: 'node-version', + weight: 1, + }, + ], + }, + ], + plugins: [ + { + audits: [ + { + slug: 'node-version', + title: 'Node version', + description: 'prints node version to file', + docsUrl: 'https://nodejs.org/', + }, + ], + runner: { + command: 'node', + args: ['-v'], + outputFile: 'output.json', + }, + groups: [], + slug: 'node', + title: 'Node.js', + icon: 'javascript', + }, + ], +} satisfies CoreConfig; diff --git a/packages/core/mocks/fixtures/configs/code-pushup.needs-tsconfig.config.ts b/packages/core/mocks/fixtures/configs/code-pushup.needs-tsconfig.config.ts new file mode 100644 index 000000000..44b4d0a2d --- /dev/null +++ b/packages/core/mocks/fixtures/configs/code-pushup.needs-tsconfig.config.ts @@ -0,0 +1,10 @@ +// the point is to test runtime import which relies on alias defined in tsconfig.json "paths" +// non-type import from '@example/custom-plugin' wouldn't work without --tsconfig +// eslint-disable-next-line import/no-unresolved +import customPlugin from '@example/custom-plugin'; + +const config = { + plugins: [customPlugin], +}; + +export default config; diff --git a/packages/core/mocks/fixtures/configs/custom-plugin.ts b/packages/core/mocks/fixtures/configs/custom-plugin.ts new file mode 100644 index 000000000..6afe6bc80 --- /dev/null +++ b/packages/core/mocks/fixtures/configs/custom-plugin.ts @@ -0,0 +1,24 @@ +const customPluginConfig = { + slug: 'good-feels', + title: 'Good feels', + icon: 'javascript', + audits: [ + { + slug: 'always-perfect', + title: 'Always perfect', + }, + ], + runner: () => [ + { + slug: 'always-perfect', + score: 1, + value: 100, + displayValue: '✅ Perfect! 👌', + }, + ], +}; + +export function customPlugin() { + return customPluginConfig; +} +export default customPluginConfig; diff --git a/packages/core/mocks/fixtures/configs/tsconfig.json b/packages/core/mocks/fixtures/configs/tsconfig.json new file mode 100644 index 000000000..42976b47b --- /dev/null +++ b/packages/core/mocks/fixtures/configs/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "paths": { + "@example/custom-plugin": ["./custom-plugin.ts"] + } + } +} diff --git a/packages/core/src/lib/implementation/read-rc-file.int.test.ts b/packages/core/src/lib/implementation/read-rc-file.int.test.ts index 59a520175..ac01c8987 100644 --- a/packages/core/src/lib/implementation/read-rc-file.int.test.ts +++ b/packages/core/src/lib/implementation/read-rc-file.int.test.ts @@ -8,12 +8,7 @@ describe('readRcByPath', () => { '..', '..', '..', - '..', - '..', - 'testing', - 'test-fixtures', - 'src', - 'lib', + 'mocks', 'fixtures', 'configs', ); diff --git a/packages/core/src/lib/implementation/read-rc-file.ts b/packages/core/src/lib/implementation/read-rc-file.ts index 090ad2c0e..fb27a0733 100644 --- a/packages/core/src/lib/implementation/read-rc-file.ts +++ b/packages/core/src/lib/implementation/read-rc-file.ts @@ -27,7 +27,6 @@ export async function readRcByPath( const result = await importModule({ filepath: filePath, tsconfig, - format: 'esm', }); return { result, message: `Imported config from ${formattedTarget}` }; }, diff --git a/packages/core/src/lib/implementation/read-rc-file.unit.test.ts b/packages/core/src/lib/implementation/read-rc-file.unit.test.ts index 9f72ca34b..c80e005fd 100644 --- a/packages/core/src/lib/implementation/read-rc-file.unit.test.ts +++ b/packages/core/src/lib/implementation/read-rc-file.unit.test.ts @@ -3,32 +3,33 @@ import { CONFIG_FILE_NAME, type CoreConfig } from '@code-pushup/models'; import { MEMFS_VOLUME } from '@code-pushup/test-utils'; import { autoloadRc } from './read-rc-file.js'; -// mock bundleRequire inside importEsmModule used for fetching config -vi.mock('bundle-require', async () => { +// mock importModule from @code-pushup/utils used for fetching config +vi.mock('@code-pushup/utils', async () => { + const utils = + await vi.importActual( + '@code-pushup/utils', + ); const { CORE_CONFIG_MOCK }: Record = await vi.importActual('@code-pushup/test-fixtures'); return { - bundleRequire: vi + ...utils, + importModule: vi .fn() - .mockImplementation((options: { filepath: string }) => { + .mockImplementation(async (options: { filepath: string }) => { const extension = options.filepath.split('.').at(-1); return { - mod: { - default: { - ...CORE_CONFIG_MOCK, - upload: { - ...CORE_CONFIG_MOCK?.upload, - project: extension, // returns loaded file extension to check format precedence - }, - }, + ...CORE_CONFIG_MOCK, + upload: { + ...CORE_CONFIG_MOCK?.upload, + project: extension, // returns loaded file extension to check format precedence }, }; }), }; }); -// Note: memfs files are only listed to satisfy a system check, value is used from bundle-require mock +// Note: memfs files are only listed to satisfy a system check, value is used from importModule mock describe('autoloadRc', () => { it('prioritise a .ts configuration file', async () => { vol.fromJSON( From 18a6c400193a4ab27a753e51b6cf719e083f7646 Mon Sep 17 00:00:00 2001 From: John Doe Date: Mon, 2 Feb 2026 21:02:09 +0100 Subject: [PATCH 3/9] fix: delete centralised mock files --- .../fixtures/configs/code-pushup.config.js | 43 --------------- .../fixtures/configs/code-pushup.config.mjs | 43 --------------- .../fixtures/configs/code-pushup.config.ts | 45 ---------------- .../configs/code-pushup.empty.config.js | 1 - .../configs/code-pushup.invalid.config.ts | 53 ------------------- .../code-pushup.needs-tsconfig.config.ts | 10 ---- .../src/lib/fixtures/configs/custom-plugin.ts | 24 --------- .../src/lib/fixtures/configs/tsconfig.json | 7 --- 8 files changed, 226 deletions(-) delete mode 100644 testing/test-fixtures/src/lib/fixtures/configs/code-pushup.config.js delete mode 100644 testing/test-fixtures/src/lib/fixtures/configs/code-pushup.config.mjs delete mode 100644 testing/test-fixtures/src/lib/fixtures/configs/code-pushup.config.ts delete mode 100644 testing/test-fixtures/src/lib/fixtures/configs/code-pushup.empty.config.js delete mode 100644 testing/test-fixtures/src/lib/fixtures/configs/code-pushup.invalid.config.ts delete mode 100644 testing/test-fixtures/src/lib/fixtures/configs/code-pushup.needs-tsconfig.config.ts delete mode 100644 testing/test-fixtures/src/lib/fixtures/configs/custom-plugin.ts delete mode 100644 testing/test-fixtures/src/lib/fixtures/configs/tsconfig.json diff --git a/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.config.js b/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.config.js deleted file mode 100644 index 9f1661d4d..000000000 --- a/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.config.js +++ /dev/null @@ -1,43 +0,0 @@ -export default { - upload: { - organization: 'code-pushup', - project: 'cli-js', - apiKey: 'e2e-api-key', - server: 'https://e2e.com/api', - }, - categories: [ - { - slug: 'category-1', - title: 'Category 1', - refs: [ - { - type: 'audit', - plugin: 'node', - slug: 'node-version', - weight: 1, - }, - ], - }, - ], - plugins: [ - { - audits: [ - { - slug: 'node-version', - title: 'Node version', - description: 'prints node version to file', - docsUrl: 'https://nodejs.org/', - }, - ], - runner: { - command: 'node', - args: ['-v'], - outputFile: 'output.json', - }, - groups: [], - slug: 'node', - title: 'Node.js', - icon: 'javascript', - }, - ], -}; diff --git a/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.config.mjs b/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.config.mjs deleted file mode 100644 index d7f531533..000000000 --- a/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.config.mjs +++ /dev/null @@ -1,43 +0,0 @@ -export default { - upload: { - organization: 'code-pushup', - project: 'cli-mjs', - apiKey: 'e2e-api-key', - server: 'https://e2e.com/api', - }, - categories: [ - { - slug: 'category-1', - title: 'Category 1', - refs: [ - { - type: 'audit', - plugin: 'node', - slug: 'node-version', - weight: 1, - }, - ], - }, - ], - plugins: [ - { - audits: [ - { - slug: 'node-version', - title: 'Node version', - description: 'prints node version to file', - docsUrl: 'https://nodejs.org/', - }, - ], - runner: { - command: 'node', - args: ['-v'], - outputFile: 'output.json', - }, - groups: [], - slug: 'node', - title: 'Node.js', - icon: 'javascript', - }, - ], -}; diff --git a/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.config.ts b/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.config.ts deleted file mode 100644 index aad20f9b6..000000000 --- a/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.config.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { type CoreConfig } from '@code-pushup/models'; - -export default { - upload: { - organization: 'code-pushup', - project: 'cli-ts', - apiKey: 'e2e-api-key', - server: 'https://e2e.com/api', - }, - categories: [ - { - slug: 'category-1', - title: 'Category 1', - refs: [ - { - type: 'audit', - plugin: 'node', - slug: 'node-version', - weight: 1, - }, - ], - }, - ], - plugins: [ - { - audits: [ - { - slug: 'node-version', - title: 'Node version', - description: 'prints node version to file', - docsUrl: 'https://nodejs.org/', - }, - ], - runner: { - command: 'node', - args: ['-v'], - outputFile: 'output.json', - }, - groups: [], - slug: 'node', - title: 'Node.js', - icon: 'javascript', - }, - ], -} satisfies CoreConfig; diff --git a/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.empty.config.js b/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.empty.config.js deleted file mode 100644 index ff8b4c563..000000000 --- a/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.empty.config.js +++ /dev/null @@ -1 +0,0 @@ -export default {}; diff --git a/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.invalid.config.ts b/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.invalid.config.ts deleted file mode 100644 index 7396948b9..000000000 --- a/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.invalid.config.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { CoreConfig } from '@code-pushup/models'; - -export default { - persist: { outputDir: 'tmp' }, - upload: { - organization: 'code-pushup', - project: 'cli-ts', - apiKey: 'e2e-api-key', - server: 'https://e2e.com/api', - }, - categories: [ - { - slug: 'bug-prevention', - title: 'Bug prevention', - // due to duplicate category references, the config is invalid - refs: [ - { - type: 'audit', - plugin: 'node', - slug: 'node-version', - weight: 1, - }, - { - type: 'audit', - plugin: 'node', - slug: 'node-version', - weight: 1, - }, - ], - }, - ], - plugins: [ - { - audits: [ - { - slug: 'node-version', - title: 'Node version', - description: 'prints node version to file', - docsUrl: 'https://nodejs.org/', - }, - ], - runner: { - command: 'node', - args: ['-v'], - outputFile: 'output.json', - }, - groups: [], - slug: 'node', - title: 'Node.js', - icon: 'javascript', - }, - ], -} satisfies CoreConfig; diff --git a/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.needs-tsconfig.config.ts b/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.needs-tsconfig.config.ts deleted file mode 100644 index 44b4d0a2d..000000000 --- a/testing/test-fixtures/src/lib/fixtures/configs/code-pushup.needs-tsconfig.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -// the point is to test runtime import which relies on alias defined in tsconfig.json "paths" -// non-type import from '@example/custom-plugin' wouldn't work without --tsconfig -// eslint-disable-next-line import/no-unresolved -import customPlugin from '@example/custom-plugin'; - -const config = { - plugins: [customPlugin], -}; - -export default config; diff --git a/testing/test-fixtures/src/lib/fixtures/configs/custom-plugin.ts b/testing/test-fixtures/src/lib/fixtures/configs/custom-plugin.ts deleted file mode 100644 index 6afe6bc80..000000000 --- a/testing/test-fixtures/src/lib/fixtures/configs/custom-plugin.ts +++ /dev/null @@ -1,24 +0,0 @@ -const customPluginConfig = { - slug: 'good-feels', - title: 'Good feels', - icon: 'javascript', - audits: [ - { - slug: 'always-perfect', - title: 'Always perfect', - }, - ], - runner: () => [ - { - slug: 'always-perfect', - score: 1, - value: 100, - displayValue: '✅ Perfect! 👌', - }, - ], -}; - -export function customPlugin() { - return customPluginConfig; -} -export default customPluginConfig; diff --git a/testing/test-fixtures/src/lib/fixtures/configs/tsconfig.json b/testing/test-fixtures/src/lib/fixtures/configs/tsconfig.json deleted file mode 100644 index 42976b47b..000000000 --- a/testing/test-fixtures/src/lib/fixtures/configs/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "paths": { - "@example/custom-plugin": ["./custom-plugin.ts"] - } - } -} From 27f92edf46c14e55c7dddc4beb4cbfaa908a009a Mon Sep 17 00:00:00 2001 From: John Doe Date: Tue, 3 Feb 2026 15:31:55 +0100 Subject: [PATCH 4/9] refactor: resolve lint issues --- packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts | 1 + packages/utils/src/lib/import-module.int.test.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts b/packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts index 76405daba..f0bb991d3 100644 --- a/packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts +++ b/packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts @@ -1,4 +1,5 @@ // the point is to test runtime import which requires tsconfig for path aliases +// eslint-disable-next-line import/no-unresolved import customPlugin from '@example/custom-plugin'; const config = { diff --git a/packages/utils/src/lib/import-module.int.test.ts b/packages/utils/src/lib/import-module.int.test.ts index 89d91aecd..181c85ce0 100644 --- a/packages/utils/src/lib/import-module.int.test.ts +++ b/packages/utils/src/lib/import-module.int.test.ts @@ -1,5 +1,5 @@ import path from 'node:path'; -import { importModule } from './import-module'; +import { importModule } from './import-module.js'; describe('importModule', () => { const mockDir = path.join( From b06c96b9160929acf7c7c672d7f20ccb079dc1ff Mon Sep 17 00:00:00 2001 From: John Doe Date: Tue, 3 Feb 2026 15:38:37 +0100 Subject: [PATCH 5/9] refactor: add defaults for backwards compat --- packages/utils/src/lib/import-module.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/lib/import-module.ts b/packages/utils/src/lib/import-module.ts index eaefb187b..6f43faaab 100644 --- a/packages/utils/src/lib/import-module.ts +++ b/packages/utils/src/lib/import-module.ts @@ -11,7 +11,10 @@ export async function importModule(options: Options): Promise { throw new Error(`Expected '${options.filepath}' to be a file`); } - const { mod } = await bundleRequire(options); + const { mod } = await bundleRequire({ + format: 'esm', + ...options, + }); if (typeof mod === 'object' && 'default' in mod) { return mod.default as T; From 30802a5cbfe6b9d825c9a1a74f7162e5d7cfaffb Mon Sep 17 00:00:00 2001 From: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:02:14 +0100 Subject: [PATCH 6/9] Update packages/plugin-lighthouse/src/lib/runner/utils.unit.test.ts Co-authored-by: Hanna Skryl <80118140+hanna-skryl@users.noreply.github.com> --- packages/plugin-lighthouse/src/lib/runner/utils.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8f05e6391..6cf6db40a 100644 --- a/packages/plugin-lighthouse/src/lib/runner/utils.unit.test.ts +++ b/packages/plugin-lighthouse/src/lib/runner/utils.unit.test.ts @@ -32,7 +32,7 @@ vi.mock('@code-pushup/utils', async () => { '@code-pushup/utils', ); const { CORE_CONFIG_MOCK }: Record = - await vi.importActual('@code-pushup/test-utils'); + await vi.importActual('@code-pushup/test-fixtures'); return { ...utils, From e8c45262b391b25cfd11c188af6d31aa44b55e37 Mon Sep 17 00:00:00 2001 From: John Doe Date: Tue, 3 Feb 2026 19:21:18 +0100 Subject: [PATCH 7/9] refactor: wip --- packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts b/packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts index f0bb991d3..e881c1e95 100644 --- a/packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts +++ b/packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts @@ -1,5 +1,6 @@ // the point is to test runtime import which requires tsconfig for path aliases // eslint-disable-next-line import/no-unresolved +// @ts-expect-error - test tsconfig paths missing in config import customPlugin from '@example/custom-plugin'; const config = { From e2685b398fba73c58c32e75cc14a38926573a56e Mon Sep 17 00:00:00 2001 From: John Doe Date: Tue, 3 Feb 2026 19:25:59 +0100 Subject: [PATCH 8/9] refactor: wip --- .../cli/mocks/configs/code-pushup.needs-tsconfig.config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts b/packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts index e881c1e95..d5d13931a 100644 --- a/packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts +++ b/packages/cli/mocks/configs/code-pushup.needs-tsconfig.config.ts @@ -1,8 +1,10 @@ // the point is to test runtime import which requires tsconfig for path aliases -// eslint-disable-next-line import/no-unresolved + +/* eslint-disable import/no-unresolved */ // @ts-expect-error - test tsconfig paths missing in config import customPlugin from '@example/custom-plugin'; +/* eslint-enable import/no-unresolved */ const config = { plugins: [customPlugin], }; From 48e900f0f05d6951032c6cad9e682898f53a3858 Mon Sep 17 00:00:00 2001 From: John Doe Date: Tue, 3 Feb 2026 22:12:45 +0100 Subject: [PATCH 9/9] refactor: remove redundant tests --- .../cli/mocks/configs/code-pushup.config.js | 43 ------------------- .../cli/mocks/configs/code-pushup.config.mjs | 43 ------------------- .../core-config.middleware.int.test.ts | 35 +++------------ 3 files changed, 7 insertions(+), 114 deletions(-) delete mode 100644 packages/cli/mocks/configs/code-pushup.config.js delete mode 100644 packages/cli/mocks/configs/code-pushup.config.mjs diff --git a/packages/cli/mocks/configs/code-pushup.config.js b/packages/cli/mocks/configs/code-pushup.config.js deleted file mode 100644 index 9f1661d4d..000000000 --- a/packages/cli/mocks/configs/code-pushup.config.js +++ /dev/null @@ -1,43 +0,0 @@ -export default { - upload: { - organization: 'code-pushup', - project: 'cli-js', - apiKey: 'e2e-api-key', - server: 'https://e2e.com/api', - }, - categories: [ - { - slug: 'category-1', - title: 'Category 1', - refs: [ - { - type: 'audit', - plugin: 'node', - slug: 'node-version', - weight: 1, - }, - ], - }, - ], - plugins: [ - { - audits: [ - { - slug: 'node-version', - title: 'Node version', - description: 'prints node version to file', - docsUrl: 'https://nodejs.org/', - }, - ], - runner: { - command: 'node', - args: ['-v'], - outputFile: 'output.json', - }, - groups: [], - slug: 'node', - title: 'Node.js', - icon: 'javascript', - }, - ], -}; diff --git a/packages/cli/mocks/configs/code-pushup.config.mjs b/packages/cli/mocks/configs/code-pushup.config.mjs deleted file mode 100644 index d7f531533..000000000 --- a/packages/cli/mocks/configs/code-pushup.config.mjs +++ /dev/null @@ -1,43 +0,0 @@ -export default { - upload: { - organization: 'code-pushup', - project: 'cli-mjs', - apiKey: 'e2e-api-key', - server: 'https://e2e.com/api', - }, - categories: [ - { - slug: 'category-1', - title: 'Category 1', - refs: [ - { - type: 'audit', - plugin: 'node', - slug: 'node-version', - weight: 1, - }, - ], - }, - ], - plugins: [ - { - audits: [ - { - slug: 'node-version', - title: 'Node version', - description: 'prints node version to file', - docsUrl: 'https://nodejs.org/', - }, - ], - runner: { - command: 'node', - args: ['-v'], - outputFile: 'output.json', - }, - groups: [], - slug: 'node', - title: 'Node.js', - icon: 'javascript', - }, - ], -}; diff --git a/packages/cli/src/lib/implementation/core-config.middleware.int.test.ts b/packages/cli/src/lib/implementation/core-config.middleware.int.test.ts index a918c35c3..f29d2cd53 100644 --- a/packages/cli/src/lib/implementation/core-config.middleware.int.test.ts +++ b/packages/cli/src/lib/implementation/core-config.middleware.int.test.ts @@ -20,22 +20,13 @@ describe('coreConfigMiddleware', () => { skipPlugins: [], }; - it.each(['ts', 'mjs', 'js'])( - 'should load a valid .%s config', - async extension => { - const config = await coreConfigMiddleware({ - config: path.join(configDirPath, `code-pushup.config.${extension}`), - ...CLI_DEFAULTS, - }); - expect(config.config).toContain(`code-pushup.config.${extension}`); - expect(config.upload?.project).toContain(extension); - }, - ); - - it('should throw with invalid config path', async () => { - await expect( - coreConfigMiddleware({ config: 'wrong/path/to/config', ...CLI_DEFAULTS }), - ).rejects.toThrow(/File '.*' does not exist/); + it('should load a valid .%s config', async () => { + const config = await coreConfigMiddleware({ + config: path.join(configDirPath, `code-pushup.config.ts`), + ...CLI_DEFAULTS, + }); + expect(config.config).toContain(`code-pushup.config.ts`); + expect(config.upload?.project).toContain('ts'); }); it('should load config which relies on provided --tsconfig', async () => { @@ -50,16 +41,4 @@ describe('coreConfigMiddleware', () => { }), ).resolves.toBeTruthy(); }); - - it('should throw if --tsconfig is missing but needed to resolve import', async () => { - await expect( - coreConfigMiddleware({ - config: path.join( - configDirPath, - 'code-pushup.needs-tsconfig.config.ts', - ), - ...CLI_DEFAULTS, - }), - ).rejects.toThrow("Cannot find package '@example/custom-plugin'"); - }); });