diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/api/code-pushup.config.js b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/api/code-pushup.config.js new file mode 100644 index 000000000..984fdd122 --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/api/code-pushup.config.js @@ -0,0 +1,3 @@ +import { resolveConfig } from '../../code-pushup.preset.js'; + +export default resolveConfig(import.meta.url); diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/api/project.json b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/api/project.json new file mode 100644 index 000000000..9a9308248 --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/api/project.json @@ -0,0 +1,9 @@ +{ + "name": "api", + "projectType": "application", + "targets": { + "code-pushup": { + "command": "npx @code-pushup/cli --no-progress --config=apps/api/code-pushup.config.js" + } + } +} diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/api/src/index.js b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/api/src/index.js new file mode 100644 index 000000000..e9fe0090d --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/api/src/index.js @@ -0,0 +1 @@ +console.log('Hello, world!'); diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/cms/code-pushup.config.js b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/cms/code-pushup.config.js new file mode 100644 index 000000000..984fdd122 --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/cms/code-pushup.config.js @@ -0,0 +1,3 @@ +import { resolveConfig } from '../../code-pushup.preset.js'; + +export default resolveConfig(import.meta.url); diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/cms/project.json b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/cms/project.json new file mode 100644 index 000000000..dfc038de3 --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/cms/project.json @@ -0,0 +1,9 @@ +{ + "name": "cms", + "projectType": "application", + "targets": { + "code-pushup": { + "command": "npx @code-pushup/cli --no-progress --config=apps/cms/code-pushup.config.js" + } + } +} diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/cms/src/index.js b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/cms/src/index.js new file mode 100644 index 000000000..e9fe0090d --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/cms/src/index.js @@ -0,0 +1 @@ +console.log('Hello, world!'); diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/web/code-pushup.config.js b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/web/code-pushup.config.js new file mode 100644 index 000000000..984fdd122 --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/web/code-pushup.config.js @@ -0,0 +1,3 @@ +import { resolveConfig } from '../../code-pushup.preset.js'; + +export default resolveConfig(import.meta.url); diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/web/project.json b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/web/project.json new file mode 100644 index 000000000..992d0e57e --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/web/project.json @@ -0,0 +1,9 @@ +{ + "name": "web", + "projectType": "application", + "targets": { + "code-pushup": { + "command": "npx @code-pushup/cli --no-progress --config=apps/web/code-pushup.config.js" + } + } +} diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/web/src/index.ts b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/web/src/index.ts new file mode 100644 index 000000000..e9fe0090d --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/apps/web/src/index.ts @@ -0,0 +1 @@ +console.log('Hello, world!'); diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/code-pushup.preset.js b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/code-pushup.preset.js new file mode 100644 index 000000000..43acc1537 --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/code-pushup.preset.js @@ -0,0 +1,66 @@ +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { crawlFileSystem } from '@code-pushup/utils'; + +export function resolveConfig(url) { + const directory = fileURLToPath(dirname(url)); + return { + persist: { + outputDir: join(directory, '.code-pushup'), + }, + plugins: [ + { + slug: 'ts-migration', + title: 'TypeScript migration', + icon: 'typescript', + audits: [ + { + slug: 'ts-files', + title: 'Source files converted from JavaScript to TypeScript', + }, + ], + runner: async () => { + const paths = await crawlFileSystem({ + directory, + pattern: /\.[jt]s$/, + }); + const jsPaths = paths.filter(path => path.endsWith('.js')); + const tsPaths = paths.filter(path => path.endsWith('.ts')); + const jsFileCount = jsPaths.length; + const tsFileCount = tsPaths.length; + const ratio = tsFileCount / (jsFileCount + tsFileCount); + const percentage = Math.round(ratio * 100); + return [ + { + slug: 'ts-files', + value: percentage, + score: ratio, + displayValue: `${percentage}% converted`, + details: { + issues: jsPaths.map(file => ({ + message: 'Use .ts file extension instead of .js', + severity: 'warning', + source: { file }, + })), + }, + }, + ]; + }, + }, + ], + categories: [ + { + slug: 'ts-migration', + title: 'TypeScript migration', + refs: [ + { + type: 'audit', + plugin: 'ts-migration', + slug: 'ts-files', + weight: 1, + }, + ], + }, + ], + }; +} diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/ui/code-pushup.config.js b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/ui/code-pushup.config.js new file mode 100644 index 000000000..984fdd122 --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/ui/code-pushup.config.js @@ -0,0 +1,3 @@ +import { resolveConfig } from '../../code-pushup.preset.js'; + +export default resolveConfig(import.meta.url); diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/ui/project.json b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/ui/project.json new file mode 100644 index 000000000..aec5ff267 --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/ui/project.json @@ -0,0 +1,9 @@ +{ + "name": "ui", + "projectType": "library", + "targets": { + "code-pushup": { + "command": "npx @code-pushup/cli --no-progress --config=libs/ui/code-pushup.config.js" + } + } +} diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/ui/src/index.test.ts b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/ui/src/index.test.ts new file mode 100644 index 000000000..76b6b6045 --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/ui/src/index.test.ts @@ -0,0 +1,6 @@ +import assert from 'node:assert'; +import test from 'node:test'; + +test('1984', () => { + assert.equal(2 + 2, 5); +}); diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/ui/src/index.ts b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/ui/src/index.ts new file mode 100644 index 000000000..e9fe0090d --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/ui/src/index.ts @@ -0,0 +1 @@ +console.log('Hello, world!'); diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/utils/code-pushup.config.js b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/utils/code-pushup.config.js new file mode 100644 index 000000000..984fdd122 --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/utils/code-pushup.config.js @@ -0,0 +1,3 @@ +import { resolveConfig } from '../../code-pushup.preset.js'; + +export default resolveConfig(import.meta.url); diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/utils/project.json b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/utils/project.json new file mode 100644 index 000000000..fc0812779 --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/utils/project.json @@ -0,0 +1,9 @@ +{ + "name": "utils", + "projectType": "library", + "targets": { + "code-pushup": { + "command": "npx @code-pushup/cli --no-progress --config=libs/utils/code-pushup.config.js" + } + } +} diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/utils/src/index.js b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/utils/src/index.js new file mode 100644 index 000000000..e9fe0090d --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/utils/src/index.js @@ -0,0 +1 @@ +console.log('Hello, world!'); diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/utils/src/index.test.js b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/utils/src/index.test.js new file mode 100644 index 000000000..76b6b6045 --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/libs/utils/src/index.test.js @@ -0,0 +1,6 @@ +import assert from 'node:assert'; +import test from 'node:test'; + +test('1984', () => { + assert.equal(2 + 2, 5); +}); diff --git a/e2e/ci-e2e/mocks/fixtures/nx-monorepo/nx.json b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/nx.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/nx-monorepo/nx.json @@ -0,0 +1 @@ +{} diff --git a/e2e/ci-e2e/mocks/setup.ts b/e2e/ci-e2e/mocks/setup.ts index 6a03ef9f4..1f7bc1c89 100644 --- a/e2e/ci-e2e/mocks/setup.ts +++ b/e2e/ci-e2e/mocks/setup.ts @@ -30,7 +30,6 @@ export async function setupTestRepo(folder: string) { TEST_OUTPUT_DIR, folder, ); - const outputDir = join(baseDir, '.code-pushup'); await cp(fixturesDir, baseDir, { recursive: true }); @@ -43,7 +42,6 @@ export async function setupTestRepo(folder: string) { return { git, baseDir, - outputDir, cleanup: () => teardownTestFolder(baseDir), }; } diff --git a/e2e/ci-e2e/tests/basic.e2e.test.ts b/e2e/ci-e2e/tests/basic.e2e.test.ts index b1685e0a7..22db1c6a7 100644 --- a/e2e/ci-e2e/tests/basic.e2e.test.ts +++ b/e2e/ci-e2e/tests/basic.e2e.test.ts @@ -44,13 +44,16 @@ describe('CI - standalone mode', () => { mode: 'standalone', files: { report: { - json: join(repo.outputDir, 'report.json'), - md: join(repo.outputDir, 'report.md'), + json: join(repo.baseDir, '.code-pushup/report.json'), + md: join(repo.baseDir, '.code-pushup/report.md'), }, }, } satisfies RunResult); - const jsonPromise = readFile(join(repo.outputDir, 'report.json'), 'utf8'); + const jsonPromise = readFile( + join(repo.baseDir, '.code-pushup/report.json'), + 'utf8', + ); await expect(jsonPromise).resolves.toBeTruthy(); const report = JSON.parse(await jsonPromise) as Report; expect(report).toEqual( @@ -98,18 +101,18 @@ describe('CI - standalone mode', () => { newIssues: [], files: { report: { - json: join(repo.outputDir, 'report.json'), - md: join(repo.outputDir, 'report.md'), + json: join(repo.baseDir, '.code-pushup/report.json'), + md: join(repo.baseDir, '.code-pushup/report.md'), }, diff: { - json: join(repo.outputDir, 'report-diff.json'), - md: join(repo.outputDir, 'report-diff.md'), + json: join(repo.baseDir, '.code-pushup/report-diff.json'), + md: join(repo.baseDir, '.code-pushup/report-diff.md'), }, }, } satisfies RunResult); const mdPromise = readFile( - join(repo.outputDir, 'report-diff.md'), + join(repo.baseDir, '.code-pushup/report-diff.md'), 'utf8', ); await expect(mdPromise).resolves.toBeTruthy(); diff --git a/e2e/ci-e2e/tests/nx-monorepo.e2e.test.ts b/e2e/ci-e2e/tests/nx-monorepo.e2e.test.ts new file mode 100644 index 000000000..7dcf93fc8 --- /dev/null +++ b/e2e/ci-e2e/tests/nx-monorepo.e2e.test.ts @@ -0,0 +1,91 @@ +import { join } from 'node:path'; +import type { SimpleGit } from 'simple-git'; +import { afterEach } from 'vitest'; +import { type Options, type RunResult, runInCI } from '@code-pushup/ci'; +import { readJsonFile } from '@code-pushup/utils'; +import { MOCK_API } from '../mocks/api'; +import { type TestRepo, setupTestRepo } from '../mocks/setup'; + +describe('CI - monorepo mode (Nx)', () => { + let repo: TestRepo; + let git: SimpleGit; + let options: Options; + + beforeEach(async () => { + repo = await setupTestRepo('nx-monorepo'); + git = repo.git; + options = { + monorepo: true, + directory: repo.baseDir, + }; + }); + + afterEach(async () => { + await repo.cleanup(); + }); + + describe('push event', () => { + beforeEach(async () => { + await git.checkout('main'); + }); + + it('should collect reports for all projects', async () => { + await expect( + runInCI( + { head: { ref: 'main', sha: await git.revparse('main') } }, + MOCK_API, + options, + git, + ), + ).resolves.toEqual({ + mode: 'monorepo', + projects: expect.arrayContaining([ + { + name: 'api', + files: { + report: { + json: join(repo.baseDir, 'apps/api/.code-pushup/report.json'), + md: join(repo.baseDir, 'apps/api/.code-pushup/report.md'), + }, + }, + }, + ]), + } satisfies RunResult); + + await expect( + readJsonFile(join(repo.baseDir, 'apps/api/.code-pushup/report.json')), + ).resolves.toEqual( + expect.objectContaining({ + plugins: [ + expect.objectContaining({ + audits: [ + expect.objectContaining({ + score: 0, + displayValue: '0% converted', + }), + ], + }), + ], + }), + ); + await expect( + readJsonFile(join(repo.baseDir, 'libs/ui/.code-pushup/report.json')), + ).resolves.toEqual( + expect.objectContaining({ + plugins: [ + expect.objectContaining({ + audits: [ + expect.objectContaining({ + score: expect.closeTo(0.666), + displayValue: '67% converted', + }), + ], + }), + ], + }), + ); + }); + }); + + // TODO: pull request event +});