diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 845def216..3e8ebe65d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,9 +120,7 @@ jobs: - name: Install dependencies run: npm ci - name: E2E test affected projects - run: npx nx affected -t nxv-e2e --exclude cli-e2e --parallel=1 - - name: E2E test cli-e2e project (due to bugs in the setup it has to run last :( ) - run: npx nx run cli-e2e:e2e-old + run: npx nx affected -t nxv-e2e --parallel=1 build: runs-on: ubuntu-latest diff --git a/code-pushup.config.ts b/code-pushup.config.ts index 9d7def723..c96b7551c 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -37,7 +37,7 @@ const config: CoreConfig = { plugins: [ fileSizePlugin({ - directory: './dist/examples/react-todos-app', + directory: './dist/packages', pattern: /\.js$/, budget: 174_080, // 170 kB }), diff --git a/e2e/ci-e2e/mocks/fixtures/code-pushup.config.ts b/e2e/ci-e2e/mocks/fixtures/ci-test-repo/code-pushup.config.ts similarity index 100% rename from e2e/ci-e2e/mocks/fixtures/code-pushup.config.ts rename to e2e/ci-e2e/mocks/fixtures/ci-test-repo/code-pushup.config.ts diff --git a/e2e/ci-e2e/mocks/fixtures/ci-test-repo/index.js b/e2e/ci-e2e/mocks/fixtures/ci-test-repo/index.js new file mode 100644 index 000000000..7f97cd8a0 --- /dev/null +++ b/e2e/ci-e2e/mocks/fixtures/ci-test-repo/index.js @@ -0,0 +1 @@ +console.log("Hello, world!") diff --git a/e2e/ci-e2e/tests/ci.e2e.test.ts b/e2e/ci-e2e/tests/ci.e2e.test.ts index 97e27d922..96d85f9ac 100644 --- a/e2e/ci-e2e/tests/ci.e2e.test.ts +++ b/e2e/ci-e2e/tests/ci.e2e.test.ts @@ -1,11 +1,4 @@ -import { - copyFile, - mkdir, - readFile, - rename, - rm, - writeFile, -} from 'node:fs/promises'; +import { cp, readFile, rename } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { @@ -14,6 +7,7 @@ import { type SimpleGit, simpleGit, } from 'simple-git'; +import { afterEach } from 'vitest'; import { type Comment, type GitRefs, @@ -22,7 +16,14 @@ import { type RunResult, runInCI, } from '@code-pushup/ci'; -import { initGitRepo } from '@code-pushup/test-utils'; +import { nxTargetProject } from '@code-pushup/test-nx-utils'; +import { teardownTestFolder } from '@code-pushup/test-setup'; +import { + E2E_ENVIRONMENTS_DIR, + TEST_OUTPUT_DIR, + TEST_SNAPSHOTS_DIR, + initGitRepo, +} from '@code-pushup/test-utils'; describe('CI package', () => { const fixturesDir = join( @@ -30,33 +31,27 @@ describe('CI package', () => { '..', 'mocks', 'fixtures', + 'ci-test-repo', ); - const workDir = join( + const ciSetupRepoDir = join( process.cwd(), - 'tmp', - 'e2e', - 'ci-e2e', - '__test__', + E2E_ENVIRONMENTS_DIR, + nxTargetProject(), + TEST_OUTPUT_DIR, 'ci-test-repo', ); - const outputDir = join(workDir, '.code-pushup'); + const outputDir = join(ciSetupRepoDir, '.code-pushup'); const options = { - directory: workDir, + directory: ciSetupRepoDir, } satisfies Options; let git: SimpleGit; beforeEach(async () => { - await rm(workDir, { recursive: true, force: true }); - await mkdir(workDir, { recursive: true }); - await copyFile( - join(fixturesDir, 'code-pushup.config.ts'), - join(workDir, 'code-pushup.config.ts'), - ); - await writeFile(join(workDir, 'index.js'), 'console.log("Hello, world!")'); + await cp(fixturesDir, ciSetupRepoDir, { recursive: true }); - git = await initGitRepo(simpleGit, { baseDir: workDir }); + git = await initGitRepo(simpleGit, { baseDir: ciSetupRepoDir }); vi.spyOn(git, 'fetch').mockResolvedValue({} as FetchResult); vi.spyOn(git, 'diffSummary').mockResolvedValue({ @@ -69,8 +64,12 @@ describe('CI package', () => { await git.commit('Initial commit'); }); + afterEach(async () => { + await teardownTestFolder(ciSetupRepoDir); + }); + afterAll(async () => { - await rm(workDir, { recursive: true, force: true }); + await teardownTestFolder(ciSetupRepoDir); }); describe('push event', () => { @@ -138,7 +137,10 @@ describe('CI package', () => { beforeEach(async () => { await git.checkoutLocalBranch('feature-1'); - await rename(join(workDir, 'index.js'), join(workDir, 'index.ts')); + await rename( + join(ciSetupRepoDir, 'index.js'), + join(ciSetupRepoDir, 'index.ts'), + ); await git.add('index.ts'); await git.commit('Convert JS file to TS'); @@ -177,7 +179,7 @@ describe('CI package', () => { const md = await mdPromise; await expect( md.replace(/[\da-f]{40}/g, '``'), - ).toMatchFileSnapshot('__snapshots__/report-diff.md'); + ).toMatchFileSnapshot(join(TEST_SNAPSHOTS_DIR, 'report-diff.md')); }); }); }); diff --git a/e2e/cli-e2e/mocks/fixtures/code-pushup.config.js b/e2e/cli-e2e/mocks/fixtures/code-pushup.config.js deleted file mode 100644 index 2a2f0c418..000000000 --- a/e2e/cli-e2e/mocks/fixtures/code-pushup.config.js +++ /dev/null @@ -1,44 +0,0 @@ -import { join } from 'node:path'; -import coveragePlugin from '@code-pushup/coverage-plugin'; -import eslintPlugin from '@code-pushup/eslint-plugin'; - -export default { - upload: { - organization: 'code-pushup', - project: 'cli-js', - apiKey: 'e2e-api-key', - server: 'https://e2e.com/api', - }, - categories: [ - { - slug: 'bug-prevention', - title: 'Bug prevention', - refs: [{ type: 'group', plugin: 'eslint', slug: 'problems', weight: 1 }], - }, - { - slug: 'code-style', - title: 'Code style', - refs: [ - { type: 'group', plugin: 'eslint', slug: 'suggestions', weight: 1 }, - ], - }, - { - slug: 'code-coverage', - title: 'Code coverage', - refs: [ - { - type: 'group', - plugin: 'coverage', - slug: 'coverage', - weight: 1, - }, - ], - }, - ], - plugins: [ - await eslintPlugin({ eslintrc: '.eslintrc.json', patterns: '**/*.ts' }), - await coveragePlugin({ - reports: [join('e2e', 'cli-e2e', 'mocks', 'fixtures', 'lcov.info')], - }), - ], -}; diff --git a/e2e/cli-e2e/mocks/fixtures/code-pushup.config.mjs b/e2e/cli-e2e/mocks/fixtures/code-pushup.config.mjs deleted file mode 100644 index ee282c54e..000000000 --- a/e2e/cli-e2e/mocks/fixtures/code-pushup.config.mjs +++ /dev/null @@ -1,44 +0,0 @@ -import { join } from 'node:path'; -import coveragePlugin from '@code-pushup/coverage-plugin'; -import eslintPlugin from '@code-pushup/eslint-plugin'; - -export default { - upload: { - organization: 'code-pushup', - project: 'cli-mjs', - apiKey: 'e2e-api-key', - server: 'https://e2e.com/api', - }, - categories: [ - { - slug: 'bug-prevention', - title: 'Bug prevention', - refs: [{ type: 'group', plugin: 'eslint', slug: 'problems', weight: 1 }], - }, - { - slug: 'code-style', - title: 'Code style', - refs: [ - { type: 'group', plugin: 'eslint', slug: 'suggestions', weight: 1 }, - ], - }, - { - slug: 'code-coverage', - title: 'Code coverage', - refs: [ - { - type: 'group', - plugin: 'coverage', - slug: 'coverage', - weight: 1, - }, - ], - }, - ], - plugins: [ - await eslintPlugin({ eslintrc: '.eslintrc.json', patterns: '**/*.ts' }), - await coveragePlugin({ - reports: [join('e2e', 'cli-e2e', 'mocks', 'fixtures', 'lcov.info')], - }), - ], -}; diff --git a/e2e/cli-e2e/mocks/fixtures/code-pushup.config.ts b/e2e/cli-e2e/mocks/fixtures/code-pushup.config.ts deleted file mode 100644 index f6f2229a8..000000000 --- a/e2e/cli-e2e/mocks/fixtures/code-pushup.config.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { join } from 'node:path'; -import coveragePlugin from '@code-pushup/coverage-plugin'; -import eslintPlugin from '@code-pushup/eslint-plugin'; -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: 'bug-prevention', - title: 'Bug prevention', - refs: [{ type: 'group', plugin: 'eslint', slug: 'problems', weight: 1 }], - }, - { - slug: 'code-style', - title: 'Code style', - refs: [ - { type: 'group', plugin: 'eslint', slug: 'suggestions', weight: 1 }, - ], - }, - { - slug: 'code-coverage', - title: 'Code coverage', - refs: [ - { - type: 'group', - plugin: 'coverage', - slug: 'coverage', - weight: 1, - }, - ], - }, - ], - plugins: [ - await eslintPlugin({ eslintrc: '.eslintrc.json', patterns: '**/*.ts' }), - await coveragePlugin({ - reports: [join('e2e', 'cli-e2e', 'mocks', 'fixtures', 'lcov.info')], - }), - ], -} satisfies CoreConfig; diff --git a/e2e/cli-e2e/mocks/fixtures/dummy-setup/code-pushup.config.js b/e2e/cli-e2e/mocks/fixtures/dummy-setup/code-pushup.config.js new file mode 100644 index 000000000..26928cba2 --- /dev/null +++ b/e2e/cli-e2e/mocks/fixtures/dummy-setup/code-pushup.config.js @@ -0,0 +1,6 @@ +import dummyPlugin, { dummyCategory } from './dummy.plugin'; + +export default { + plugins: [dummyPlugin()], + categories: [dummyCategory], +}; diff --git a/e2e/cli-e2e/mocks/fixtures/dummy-setup/code-pushup.config.mjs b/e2e/cli-e2e/mocks/fixtures/dummy-setup/code-pushup.config.mjs new file mode 100644 index 000000000..26928cba2 --- /dev/null +++ b/e2e/cli-e2e/mocks/fixtures/dummy-setup/code-pushup.config.mjs @@ -0,0 +1,6 @@ +import dummyPlugin, { dummyCategory } from './dummy.plugin'; + +export default { + plugins: [dummyPlugin()], + categories: [dummyCategory], +}; diff --git a/e2e/cli-e2e/mocks/fixtures/dummy-setup/code-pushup.config.ts b/e2e/cli-e2e/mocks/fixtures/dummy-setup/code-pushup.config.ts new file mode 100644 index 000000000..26928cba2 --- /dev/null +++ b/e2e/cli-e2e/mocks/fixtures/dummy-setup/code-pushup.config.ts @@ -0,0 +1,6 @@ +import dummyPlugin, { dummyCategory } from './dummy.plugin'; + +export default { + plugins: [dummyPlugin()], + categories: [dummyCategory], +}; diff --git a/e2e/cli-e2e/mocks/fixtures/dummy-setup/dummy.plugin.ts b/e2e/cli-e2e/mocks/fixtures/dummy-setup/dummy.plugin.ts new file mode 100644 index 000000000..8ff5ebd18 --- /dev/null +++ b/e2e/cli-e2e/mocks/fixtures/dummy-setup/dummy.plugin.ts @@ -0,0 +1,50 @@ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import type { PluginConfig } from '@code-pushup/models'; + +export const dummyPluginSlug = 'dummy-plugin'; + +const dummyAuditSlug = 'dummy-audit'; +export const dummyAudit = { + slug: dummyAuditSlug, + title: 'Dummy Audit', + description: 'A dummy audit to test the cli.', +}; + +export const dummyCategory = { + slug: 'dummy-category', + title: 'Dummy Category', + refs: [ + { + type: 'audit', + plugin: dummyPluginSlug, + slug: dummyAuditSlug, + weight: 1, + }, + ], +}; + +export function create(): PluginConfig { + return { + slug: dummyPluginSlug, + title: 'Dummy Plugin', + icon: 'folder-javascript', + description: 'A dummy plugin to test the cli.', + runner: async () => { + const itemCount = JSON.parse( + await readFile(join('src', 'items.json'), 'utf-8'), + ).length; + return [ + { + ...dummyAudit, + slug: dummyAuditSlug, + score: itemCount < 10 ? itemCount / 10 : 1, + value: itemCount, + }, + ]; + }, + audits: [dummyAudit], + }; +} + +export default create; diff --git a/e2e/cli-e2e/mocks/fixtures/dummy-setup/src/items.json b/e2e/cli-e2e/mocks/fixtures/dummy-setup/src/items.json new file mode 100644 index 000000000..b5d8bb58d --- /dev/null +++ b/e2e/cli-e2e/mocks/fixtures/dummy-setup/src/items.json @@ -0,0 +1 @@ +[1, 2, 3] diff --git a/e2e/cli-e2e/mocks/fixtures/existing-reports/.code-pushup/source-report.json b/e2e/cli-e2e/mocks/fixtures/existing-reports/.code-pushup/source-report.json new file mode 100644 index 000000000..173781d06 --- /dev/null +++ b/e2e/cli-e2e/mocks/fixtures/existing-reports/.code-pushup/source-report.json @@ -0,0 +1,45 @@ +{ + "commit": { + "hash": "11f3153b8df2f5e651a1c72d3ddde3a0f400b8a2", + "message": "Merge branch 'main' into split-compare-e2e", + "date": "2024-11-19T14:28:43.000Z", + "author": "Michael" + }, + "packageName": "@code-pushup/core", + "version": "0.54.0", + "date": "2024-11-19T14:50:29.040Z", + "duration": 64, + "categories": [ + { + "slug": "dummy-category", + "refs": [ + { + "slug": "dummy-audit", + "weight": 1, + "type": "audit", + "plugin": "dummy-plugin" + } + ], + "title": "Dummy Category" + } + ], + "plugins": [ + { + "title": "Dummy Plugin", + "slug": "dummy-plugin", + "icon": "folder-javascript", + "date": "2024-11-19T14:50:29.080Z", + "duration": 1, + "audits": [ + { + "slug": "dummy-audit", + "value": 3, + "score": 0.3, + "title": "Dummy Audit", + "description": "A dummy audit to test the cli." + } + ], + "description": "A dummy plugin to test the cli." + } + ] +} diff --git a/e2e/cli-e2e/mocks/fixtures/existing-reports/.code-pushup/source-report.md b/e2e/cli-e2e/mocks/fixtures/existing-reports/.code-pushup/source-report.md new file mode 100644 index 000000000..2cc93822a --- /dev/null +++ b/e2e/cli-e2e/mocks/fixtures/existing-reports/.code-pushup/source-report.md @@ -0,0 +1,37 @@ +# Code PushUp Report + +| 🏷 Category | ⭐ Score | πŸ›‘ Audits | +| :-------------------------------- | :-------: | :-------: | +| [Dummy Category](#dummy-category) | πŸ”΄ **30** | 1 | + +## 🏷 Categories + +### Dummy Category + +πŸ”΄ Score: **30** + +- πŸŸ₯ [Dummy Audit](#dummy-audit-dummy-plugin) (_Dummy Plugin_) - **3** + +## πŸ›‘οΈ Audits + +### Dummy Audit (Dummy Plugin) + +πŸŸ₯ **3** (score: 30) + +A dummy audit to test the cli. + +## About + +Report was created by [Code PushUp](https://github.com/code-pushup/cli#readme) on Tue, Nov 19, 2024, 3:50 PM GMT+1. + +| Plugin | Audits | Version | Duration | +| :----------- | :----: | :-----: | -------: | +| Dummy Plugin | 1 | | 1 ms | + +| Commit | Version | Duration | Plugins | Categories | Audits | +| :------------------------------------------------------------------------------------ | :------: | -------: | :-----: | :--------: | :----: | +| Merge branch 'main' into split-compare-e2e (11f3153b8df2f5e651a1c72d3ddde3a0f400b8a2) | `0.54.0` | 64 ms | 1 | 1 | 1 | + +--- + +Made with ❀ by [Code PushUp](https://github.com/code-pushup/cli#readme) diff --git a/e2e/cli-e2e/mocks/fixtures/existing-reports/.code-pushup/target-report.json b/e2e/cli-e2e/mocks/fixtures/existing-reports/.code-pushup/target-report.json new file mode 100644 index 000000000..2d46055c3 --- /dev/null +++ b/e2e/cli-e2e/mocks/fixtures/existing-reports/.code-pushup/target-report.json @@ -0,0 +1,45 @@ +{ + "commit": { + "hash": "11f3153b8df2f5e651a1c72d3ddde3a0f400b8a2", + "message": "Merge branch 'main' into split-compare-e2e", + "date": "2024-11-19T14:28:43.000Z", + "author": "Michael" + }, + "packageName": "@code-pushup/core", + "version": "0.54.0", + "date": "2024-11-19T14:51:03.174Z", + "duration": 63, + "categories": [ + { + "slug": "dummy-category", + "refs": [ + { + "slug": "dummy-audit", + "weight": 1, + "type": "audit", + "plugin": "dummy-plugin" + } + ], + "title": "Dummy Category" + } + ], + "plugins": [ + { + "title": "Dummy Plugin", + "slug": "dummy-plugin", + "icon": "folder-javascript", + "date": "2024-11-19T14:51:03.212Z", + "duration": 1, + "audits": [ + { + "slug": "dummy-audit", + "value": 7, + "score": 0.7, + "title": "Dummy Audit", + "description": "A dummy audit to test the cli." + } + ], + "description": "A dummy plugin to test the cli." + } + ] +} diff --git a/e2e/cli-e2e/mocks/fixtures/existing-reports/.code-pushup/target-report.md b/e2e/cli-e2e/mocks/fixtures/existing-reports/.code-pushup/target-report.md new file mode 100644 index 000000000..6deba652b --- /dev/null +++ b/e2e/cli-e2e/mocks/fixtures/existing-reports/.code-pushup/target-report.md @@ -0,0 +1,37 @@ +# Code PushUp Report + +| 🏷 Category | ⭐ Score | πŸ›‘ Audits | +| :-------------------------------- | :-------: | :-------: | +| [Dummy Category](#dummy-category) | 🟑 **70** | 1 | + +## 🏷 Categories + +### Dummy Category + +🟑 Score: **70** + +- 🟨 [Dummy Audit](#dummy-audit-dummy-plugin) (_Dummy Plugin_) - **7** + +## πŸ›‘οΈ Audits + +### Dummy Audit (Dummy Plugin) + +🟨 **7** (score: 70) + +A dummy audit to test the cli. + +## About + +Report was created by [Code PushUp](https://github.com/code-pushup/cli#readme) on Tue, Nov 19, 2024, 3:51 PM GMT+1. + +| Plugin | Audits | Version | Duration | +| :----------- | :----: | :-----: | -------: | +| Dummy Plugin | 1 | | 1 ms | + +| Commit | Version | Duration | Plugins | Categories | Audits | +| :------------------------------------------------------------------------------------ | :------: | -------: | :-----: | :--------: | :----: | +| Merge branch 'main' into split-compare-e2e (11f3153b8df2f5e651a1c72d3ddde3a0f400b8a2) | `0.54.0` | 63 ms | 1 | 1 | 1 | + +--- + +Made with ❀ by [Code PushUp](https://github.com/code-pushup/cli#readme) diff --git a/e2e/cli-e2e/mocks/fixtures/existing-reports/code-pushup.config.ts b/e2e/cli-e2e/mocks/fixtures/existing-reports/code-pushup.config.ts new file mode 100644 index 000000000..26928cba2 --- /dev/null +++ b/e2e/cli-e2e/mocks/fixtures/existing-reports/code-pushup.config.ts @@ -0,0 +1,6 @@ +import dummyPlugin, { dummyCategory } from './dummy.plugin'; + +export default { + plugins: [dummyPlugin()], + categories: [dummyCategory], +}; diff --git a/e2e/cli-e2e/mocks/fixtures/existing-reports/dummy.plugin.ts b/e2e/cli-e2e/mocks/fixtures/existing-reports/dummy.plugin.ts new file mode 100644 index 000000000..8ff5ebd18 --- /dev/null +++ b/e2e/cli-e2e/mocks/fixtures/existing-reports/dummy.plugin.ts @@ -0,0 +1,50 @@ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import type { PluginConfig } from '@code-pushup/models'; + +export const dummyPluginSlug = 'dummy-plugin'; + +const dummyAuditSlug = 'dummy-audit'; +export const dummyAudit = { + slug: dummyAuditSlug, + title: 'Dummy Audit', + description: 'A dummy audit to test the cli.', +}; + +export const dummyCategory = { + slug: 'dummy-category', + title: 'Dummy Category', + refs: [ + { + type: 'audit', + plugin: dummyPluginSlug, + slug: dummyAuditSlug, + weight: 1, + }, + ], +}; + +export function create(): PluginConfig { + return { + slug: dummyPluginSlug, + title: 'Dummy Plugin', + icon: 'folder-javascript', + description: 'A dummy plugin to test the cli.', + runner: async () => { + const itemCount = JSON.parse( + await readFile(join('src', 'items.json'), 'utf-8'), + ).length; + return [ + { + ...dummyAudit, + slug: dummyAuditSlug, + score: itemCount < 10 ? itemCount / 10 : 1, + value: itemCount, + }, + ]; + }, + audits: [dummyAudit], + }; +} + +export default create; diff --git a/e2e/cli-e2e/mocks/fixtures/lcov.info b/e2e/cli-e2e/mocks/fixtures/lcov.info deleted file mode 100644 index 474ad74e7..000000000 --- a/e2e/cli-e2e/mocks/fixtures/lcov.info +++ /dev/null @@ -1,78 +0,0 @@ -TN: -SF:src\lib\partly-covered\utils.ts -FN:2,formatReportScore -FN:6,calcDuration -FNF:2 -FNH:1 -FNDA:0,formatReportScore -FNDA:6,calcDuration -DA:1,1 -DA:2,1 -DA:3,1 -DA:4,1 -DA:5,1 -DA:6,1 -DA:7,0 -DA:8,0 -DA:9,0 -DA:10,1 -LF:10 -LH:7 -BRDA:1,0,0,6 -BRDA:1,1,0,5 -BRDA:2,4,0,1 -BRDA:4,5,0,17 -BRDA:5,6,0,4 -BRDA:6,7,0,13 -BRDA:6,10,1,0 -BRDA:7,11,0,3 -BRDA:10,12,0,12 -BRDA:10,13,1,0 -BRF:10 -BRH:8 -end_of_record -SF:src\lib\not-covered\sorting.ts -FN:1,sortReport -FNF:1 -FNH:0 -FNDA:0,sortReport -DA:1,0 -DA:2,0 -DA:3,0 -DA:4,0 -DA:5,0 -LF:5 -LH:0 -BRDA:7,1,0,0 -BRDA:7,2,1,0 -BRF:2 -BRH:0 -end_of_record -TN: -SF:src\lib\fully-covered\scoring.ts -FN:2,scoreReport -FN:8,calculateScore -FNF:2 -FNH:2 -FNDA:3,scoreReport -FNDA:5,calculateScore -DA:1,1 -DA:2,1 -DA:3,1 -DA:4,1 -DA:5,1 -DA:6,1 -DA:7,1 -DA:8,1 -DA:9,1 -DA:10,1 -LF:10 -LH:10 -BRDA:1,0,0,5 -BRDA:2,1,0,1 -BRDA:2,2,1,4 -BRDA:2,3,2,3 -BRDA:6,4,0,4 -BRF:5 -BRH:5 -end_of_record diff --git a/e2e/cli-e2e/project.json b/e2e/cli-e2e/project.json index 5bef1175d..d06a9fbea 100644 --- a/e2e/cli-e2e/project.json +++ b/e2e/cli-e2e/project.json @@ -11,22 +11,13 @@ "lintFilePatterns": ["e2e/cli-e2e/**/*.ts"] } }, - "e2e-old": { + "e2e": { "executor": "@nx/vite:test", "options": { "configFile": "e2e/cli-e2e/vite.config.e2e.ts" } } }, - "implicitDependencies": [ - "models", - "utils", - "core", - "cli", - "plugin-eslint", - "plugin-coverage", - "plugin-js-packages", - "react-todos-app" - ], + "implicitDependencies": ["cli"], "tags": ["scope:core", "scope:plugin", "type:e2e"] } diff --git a/e2e/cli-e2e/tests/__snapshots__/collect.e2e.test.ts.snap b/e2e/cli-e2e/tests/__snapshots__/collect.e2e.test.ts.snap deleted file mode 100644 index 35945e037..000000000 --- a/e2e/cli-e2e/tests/__snapshots__/collect.e2e.test.ts.snap +++ /dev/null @@ -1,1001 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`CLI collect > should run ESLint plugin and create report.json 1`] = ` -{ - "categories": [ - { - "refs": [ - { - "plugin": "eslint", - "slug": "no-cond-assign", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-const-assign", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-debugger", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-invalid-regexp", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-undef", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-unreachable-loop", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-unsafe-negation", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-unsafe-optional-chaining", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "use-isnan", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "valid-typeof", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "eqeqeq", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "react-jsx-key", - "type": "audit", - "weight": 2, - }, - { - "plugin": "eslint", - "slug": "react-prop-types", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "react-react-in-jsx-scope", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "react-hooks-rules-of-hooks", - "type": "audit", - "weight": 2, - }, - { - "plugin": "eslint", - "slug": "react-hooks-exhaustive-deps", - "type": "audit", - "weight": 2, - }, - ], - "slug": "bug-prevention", - "title": "Bug prevention", - }, - { - "refs": [ - { - "plugin": "eslint", - "slug": "no-unused-vars", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "arrow-body-style", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "camelcase", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "curly", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "eqeqeq", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "max-lines-per-function", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "max-lines", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "object-shorthand", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "prefer-arrow-callback", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "prefer-const", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "prefer-object-spread", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "yoda", - "type": "audit", - "weight": 1, - }, - { - "plugin": "eslint", - "slug": "no-var", - "type": "audit", - "weight": 1, - }, - ], - "slug": "code-style", - "title": "Code style", - }, - ], - "packageName": "@code-pushup/core", - "plugins": [ - { - "audits": [ - { - "description": "ESLint rule **no-cond-assign**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-cond-assign", - "score": 1, - "slug": "no-cond-assign", - "title": "Disallow assignment operators in conditional expressions", - "value": 0, - }, - { - "description": "ESLint rule **no-const-assign**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-const-assign", - "score": 1, - "slug": "no-const-assign", - "title": "Disallow reassigning \`const\` variables", - "value": 0, - }, - { - "description": "ESLint rule **no-debugger**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-debugger", - "score": 1, - "slug": "no-debugger", - "title": "Disallow the use of \`debugger\`", - "value": 0, - }, - { - "description": "ESLint rule **no-invalid-regexp**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-invalid-regexp", - "score": 1, - "slug": "no-invalid-regexp", - "title": "Disallow invalid regular expression strings in \`RegExp\` constructors", - "value": 0, - }, - { - "description": "ESLint rule **no-undef**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-undef", - "score": 1, - "slug": "no-undef", - "title": "Disallow the use of undeclared variables unless mentioned in \`/*global */\` comments", - "value": 0, - }, - { - "description": "ESLint rule **no-unreachable-loop**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-unreachable-loop", - "score": 1, - "slug": "no-unreachable-loop", - "title": "Disallow loops with a body that allows only one iteration", - "value": 0, - }, - { - "description": "ESLint rule **no-unsafe-negation**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-unsafe-negation", - "score": 1, - "slug": "no-unsafe-negation", - "title": "Disallow negating the left operand of relational operators", - "value": 0, - }, - { - "description": "ESLint rule **no-unsafe-optional-chaining**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining", - "score": 1, - "slug": "no-unsafe-optional-chaining", - "title": "Disallow use of optional chaining in contexts where the \`undefined\` value is not allowed", - "value": 0, - }, - { - "description": "ESLint rule **no-unused-vars**.", - "details": { - "issues": [ - { - "message": "'loading' is assigned a value but never used.", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/App.jsx", - "position": { - "endColumn": 18, - "endLine": 8, - "startColumn": 11, - "startLine": 8, - }, - }, - }, - ], - }, - "displayValue": "1 warning", - "docsUrl": "https://eslint.org/docs/latest/rules/no-unused-vars", - "score": 0, - "slug": "no-unused-vars", - "title": "Disallow unused variables", - "value": 1, - }, - { - "description": "ESLint rule **use-isnan**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/use-isnan", - "score": 1, - "slug": "use-isnan", - "title": "Require calls to \`isNaN()\` when checking for \`NaN\`", - "value": 0, - }, - { - "description": "ESLint rule **valid-typeof**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/valid-typeof", - "score": 1, - "slug": "valid-typeof", - "title": "Enforce comparing \`typeof\` expressions against valid strings", - "value": 0, - }, - { - "description": "ESLint rule **arrow-body-style**.", - "details": { - "issues": [ - { - "message": "Unexpected block statement surrounding arrow body; move the returned value immediately after the \`=>\`.", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/components/TodoFilter.jsx", - "position": { - "endColumn": 2, - "endLine": 25, - "startColumn": 29, - "startLine": 3, - }, - }, - }, - ], - }, - "displayValue": "1 warning", - "docsUrl": "https://eslint.org/docs/latest/rules/arrow-body-style", - "score": 0, - "slug": "arrow-body-style", - "title": "Require braces around arrow function bodies", - "value": 1, - }, - { - "description": "ESLint rule **camelcase**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/camelcase", - "score": 1, - "slug": "camelcase", - "title": "Enforce camelcase naming convention", - "value": 0, - }, - { - "description": "ESLint rule **curly**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/curly", - "score": 1, - "slug": "curly", - "title": "Enforce consistent brace style for all control statements", - "value": 0, - }, - { - "description": "ESLint rule **eqeqeq**.", - "details": { - "issues": [ - { - "message": "Expected '===' and instead saw '=='.", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/hooks/useTodos.js", - "position": { - "endColumn": 43, - "endLine": 41, - "startColumn": 41, - "startLine": 41, - }, - }, - }, - ], - }, - "displayValue": "1 warning", - "docsUrl": "https://eslint.org/docs/latest/rules/eqeqeq", - "score": 0, - "slug": "eqeqeq", - "title": "Require the use of \`===\` and \`!==\`", - "value": 1, - }, - { - "description": "ESLint rule **max-lines-per-function**.", - "details": { - "issues": [ - { - "message": "Arrow function has too many lines (71). Maximum allowed is 50.", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/hooks/useTodos.js", - "position": { - "endColumn": 2, - "endLine": 73, - "startColumn": 25, - "startLine": 3, - }, - }, - }, - ], - }, - "displayValue": "1 warning", - "docsUrl": "https://eslint.org/docs/latest/rules/max-lines-per-function", - "score": 0, - "slug": "max-lines-per-function", - "title": "Enforce a maximum number of lines of code in a function", - "value": 1, - }, - { - "description": "ESLint rule **max-lines**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/max-lines", - "score": 1, - "slug": "max-lines", - "title": "Enforce a maximum number of lines per file", - "value": 0, - }, - { - "description": "ESLint rule **no-shadow**.", - "details": { - "issues": [ - { - "message": "'data' is already declared in the upper scope on line 5 column 10.", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/hooks/useTodos.js", - "position": { - "endColumn": 17, - "endLine": 11, - "startColumn": 13, - "startLine": 11, - }, - }, - }, - { - "message": "'data' is already declared in the upper scope on line 5 column 10.", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/hooks/useTodos.js", - "position": { - "endColumn": 21, - "endLine": 29, - "startColumn": 17, - "startLine": 29, - }, - }, - }, - { - "message": "'data' is already declared in the upper scope on line 5 column 10.", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/hooks/useTodos.js", - "position": { - "endColumn": 17, - "endLine": 41, - "startColumn": 13, - "startLine": 41, - }, - }, - }, - ], - }, - "displayValue": "3 warnings", - "docsUrl": "https://eslint.org/docs/latest/rules/no-shadow", - "score": 0, - "slug": "no-shadow", - "title": "Disallow variable declarations from shadowing variables declared in the outer scope", - "value": 3, - }, - { - "description": "ESLint rule **no-var**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-var", - "score": 1, - "slug": "no-var", - "title": "Require \`let\` or \`const\` instead of \`var\`", - "value": 0, - }, - { - "description": "ESLint rule **object-shorthand**.", - "details": { - "issues": [ - { - "message": "Expected property shorthand.", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/hooks/useTodos.js", - "position": { - "endColumn": 19, - "endLine": 19, - "startColumn": 7, - "startLine": 19, - }, - }, - }, - { - "message": "Expected property shorthand.", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/hooks/useTodos.js", - "position": { - "endColumn": 19, - "endLine": 32, - "startColumn": 13, - "startLine": 32, - }, - }, - }, - { - "message": "Expected property shorthand.", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/hooks/useTodos.js", - "position": { - "endColumn": 25, - "endLine": 33, - "startColumn": 13, - "startLine": 33, - }, - }, - }, - ], - }, - "displayValue": "3 warnings", - "docsUrl": "https://eslint.org/docs/latest/rules/object-shorthand", - "score": 0, - "slug": "object-shorthand", - "title": "Require or disallow method and property shorthand syntax for object literals", - "value": 3, - }, - { - "description": "ESLint rule **prefer-arrow-callback**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/prefer-arrow-callback", - "score": 1, - "slug": "prefer-arrow-callback", - "title": "Require using arrow functions for callbacks", - "value": 0, - }, - { - "description": "ESLint rule **prefer-const**.", - "details": { - "issues": [ - { - "message": "'root' is never reassigned. Use 'const' instead.", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/index.jsx", - "position": { - "endColumn": 9, - "endLine": 5, - "startColumn": 5, - "startLine": 5, - }, - }, - }, - ], - }, - "displayValue": "1 warning", - "docsUrl": "https://eslint.org/docs/latest/rules/prefer-const", - "score": 0, - "slug": "prefer-const", - "title": "Require \`const\` declarations for variables that are never reassigned after declared", - "value": 1, - }, - { - "description": "ESLint rule **prefer-object-spread**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/prefer-object-spread", - "score": 1, - "slug": "prefer-object-spread", - "title": "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead", - "value": 0, - }, - { - "description": "ESLint rule **yoda**.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/yoda", - "score": 1, - "slug": "yoda", - "title": "Require or disallow "Yoda" conditions", - "value": 0, - }, - { - "description": "ESLint rule **jsx-key**, from _react_ plugin.", - "details": { - "issues": [ - { - "message": "Missing "key" prop for element in iterator", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/components/TodoList.jsx", - "position": { - "endColumn": 12, - "endLine": 28, - "startColumn": 7, - "startLine": 7, - }, - }, - }, - ], - }, - "displayValue": "1 warning", - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-key.md", - "score": 0, - "slug": "react-jsx-key", - "title": "Disallow missing \`key\` props in iterators/collection literals", - "value": 1, - }, - { - "description": "ESLint rule **prop-types**, from _react_ plugin.", - "details": { - "issues": [ - { - "message": "'onCreate' is missing in props validation", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/components/CreateTodo.jsx", - "position": { - "endColumn": 23, - "endLine": 15, - "startColumn": 15, - "startLine": 15, - }, - }, - }, - { - "message": "'setQuery' is missing in props validation", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/components/TodoFilter.jsx", - "position": { - "endColumn": 25, - "endLine": 10, - "startColumn": 17, - "startLine": 10, - }, - }, - }, - { - "message": "'setHideComplete' is missing in props validation", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/components/TodoFilter.jsx", - "position": { - "endColumn": 34, - "endLine": 18, - "startColumn": 19, - "startLine": 18, - }, - }, - }, - { - "message": "'todos' is missing in props validation", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/components/TodoList.jsx", - "position": { - "endColumn": 17, - "endLine": 6, - "startColumn": 12, - "startLine": 6, - }, - }, - }, - { - "message": "'todos.map' is missing in props validation", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/components/TodoList.jsx", - "position": { - "endColumn": 21, - "endLine": 6, - "startColumn": 18, - "startLine": 6, - }, - }, - }, - { - "message": "'onEdit' is missing in props validation", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/components/TodoList.jsx", - "position": { - "endColumn": 27, - "endLine": 13, - "startColumn": 21, - "startLine": 13, - }, - }, - }, - ], - }, - "displayValue": "6 warnings", - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/prop-types.md", - "score": 0, - "slug": "react-prop-types", - "title": "Disallow missing props validation in a React component definition", - "value": 6, - }, - { - "description": "ESLint rule **react-in-jsx-scope**, from _react_ plugin.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/react-in-jsx-scope.md", - "score": 1, - "slug": "react-react-in-jsx-scope", - "title": "Disallow missing React when using JSX", - "value": 0, - }, - { - "description": "ESLint rule **jsx-uses-vars**, from _react_ plugin.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-uses-vars.md", - "score": 1, - "slug": "react-jsx-uses-vars", - "title": "Disallow variables used in JSX to be incorrectly marked as unused", - "value": 0, - }, - { - "description": "ESLint rule **jsx-uses-react**, from _react_ plugin.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-uses-react.md", - "score": 1, - "slug": "react-jsx-uses-react", - "title": "Disallow React to be incorrectly marked as unused", - "value": 0, - }, - { - "description": "ESLint rule **rules-of-hooks**, from _react-hooks_ plugin.", - "details": { - "issues": [], - }, - "displayValue": "passed", - "docsUrl": "https://reactjs.org/docs/hooks-rules.html", - "score": 1, - "slug": "react-hooks-rules-of-hooks", - "title": "enforces the Rules of Hooks", - "value": 0, - }, - { - "description": "ESLint rule **exhaustive-deps**, from _react-hooks_ plugin.", - "details": { - "issues": [ - { - "message": "React Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/hooks/useTodos.js", - "position": { - "endColumn": 31, - "endLine": 17, - "startColumn": 20, - "startLine": 17, - }, - }, - }, - { - "message": "React Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?", - "severity": "warning", - "source": { - "file": "examples/react-todos-app/src/hooks/useTodos.js", - "position": { - "endColumn": 29, - "endLine": 40, - "startColumn": 18, - "startLine": 40, - }, - }, - }, - ], - }, - "displayValue": "2 warnings", - "docsUrl": "https://github.com/facebook/react/issues/14920", - "score": 0, - "slug": "react-hooks-exhaustive-deps", - "title": "verifies the list of dependencies for Hooks like useEffect and similar", - "value": 2, - }, - ], - "description": "Official Code PushUp ESLint plugin", - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "groups": [ - { - "description": "Code that either will cause an error or may cause confusing behavior. Developers should consider this a high priority to resolve.", - "refs": [ - { - "slug": "no-cond-assign", - "weight": 1, - }, - { - "slug": "no-const-assign", - "weight": 1, - }, - { - "slug": "no-debugger", - "weight": 1, - }, - { - "slug": "no-invalid-regexp", - "weight": 1, - }, - { - "slug": "no-undef", - "weight": 1, - }, - { - "slug": "no-unreachable-loop", - "weight": 1, - }, - { - "slug": "no-unsafe-negation", - "weight": 1, - }, - { - "slug": "no-unsafe-optional-chaining", - "weight": 1, - }, - { - "slug": "no-unused-vars", - "weight": 1, - }, - { - "slug": "use-isnan", - "weight": 1, - }, - { - "slug": "valid-typeof", - "weight": 1, - }, - { - "slug": "react-hooks-rules-of-hooks", - "weight": 1, - }, - ], - "slug": "problems", - "title": "Problems", - }, - { - "description": "Something that could be done in a better way but no errors will occur if the code isn't changed.", - "refs": [ - { - "slug": "arrow-body-style", - "weight": 1, - }, - { - "slug": "camelcase", - "weight": 1, - }, - { - "slug": "curly", - "weight": 1, - }, - { - "slug": "eqeqeq", - "weight": 1, - }, - { - "slug": "max-lines-per-function", - "weight": 1, - }, - { - "slug": "max-lines", - "weight": 1, - }, - { - "slug": "no-shadow", - "weight": 1, - }, - { - "slug": "no-var", - "weight": 1, - }, - { - "slug": "object-shorthand", - "weight": 1, - }, - { - "slug": "prefer-arrow-callback", - "weight": 1, - }, - { - "slug": "prefer-const", - "weight": 1, - }, - { - "slug": "prefer-object-spread", - "weight": 1, - }, - { - "slug": "yoda", - "weight": 1, - }, - { - "slug": "react-hooks-exhaustive-deps", - "weight": 1, - }, - ], - "slug": "suggestions", - "title": "Suggestions", - }, - { - "refs": [ - { - "slug": "react-prop-types", - "weight": 1, - }, - { - "slug": "react-jsx-uses-vars", - "weight": 1, - }, - { - "slug": "react-jsx-uses-react", - "weight": 1, - }, - ], - "slug": "react-best-practices", - "title": "Best Practices (react)", - }, - { - "refs": [ - { - "slug": "react-jsx-key", - "weight": 1, - }, - { - "slug": "react-react-in-jsx-scope", - "weight": 1, - }, - ], - "slug": "react-possible-errors", - "title": "Possible Errors (react)", - }, - ], - "icon": "eslint", - "packageName": "@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - ], -} -`; diff --git a/e2e/cli-e2e/tests/__snapshots__/compare.e2e.test.ts.snap b/e2e/cli-e2e/tests/__snapshots__/compare.e2e.test.ts.snap index 33ededed0..c062cae7d 100644 --- a/e2e/cli-e2e/tests/__snapshots__/compare.e2e.test.ts.snap +++ b/e2e/cli-e2e/tests/__snapshots__/compare.e2e.test.ts.snap @@ -6,522 +6,52 @@ exports[`CLI compare > should compare report.json files and create report-diff.j "added": [], "changed": [ { - "displayValues": { - "after": "passed", - "before": "1 warning", - }, - "docsUrl": "https://eslint.org/docs/latest/rules/arrow-body-style", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "scores": { - "after": 1, - "before": 0, - "diff": 1, - }, - "slug": "arrow-body-style", - "title": "Require braces around arrow function bodies", - "values": { - "after": 0, - "before": 1, - "diff": -1, - }, - }, - { - "displayValues": { - "after": "passed", - "before": "3 warnings", - }, - "docsUrl": "https://eslint.org/docs/latest/rules/object-shorthand", + "displayValues": {}, "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", + "slug": "dummy-plugin", + "title": "Dummy Plugin", }, "scores": { - "after": 1, - "before": 0, - "diff": 1, + "after": 0.7, + "before": 0.3, + "diff": 0.39999999999999997, }, - "slug": "object-shorthand", - "title": "Require or disallow method and property shorthand syntax for object literals", + "slug": "dummy-audit", + "title": "Dummy Audit", "values": { - "after": 0, + "after": 7, "before": 3, - "diff": -3, - }, - }, - { - "displayValues": { - "after": "passed", - "before": "1 warning", - }, - "docsUrl": "https://eslint.org/docs/latest/rules/prefer-const", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "scores": { - "after": 1, - "before": 0, - "diff": 1, - }, - "slug": "prefer-const", - "title": "Require \`const\` declarations for variables that are never reassigned after declared", - "values": { - "after": 0, - "before": 1, - "diff": -1, + "diff": 4, }, }, ], "removed": [], - "unchanged": [ - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-cond-assign", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "no-cond-assign", - "title": "Disallow assignment operators in conditional expressions", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-const-assign", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "no-const-assign", - "title": "Disallow reassigning \`const\` variables", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-debugger", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "no-debugger", - "title": "Disallow the use of \`debugger\`", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-invalid-regexp", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "no-invalid-regexp", - "title": "Disallow invalid regular expression strings in \`RegExp\` constructors", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-undef", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "no-undef", - "title": "Disallow the use of undeclared variables unless mentioned in \`/*global */\` comments", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-unreachable-loop", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "no-unreachable-loop", - "title": "Disallow loops with a body that allows only one iteration", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-unsafe-negation", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "no-unsafe-negation", - "title": "Disallow negating the left operand of relational operators", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "no-unsafe-optional-chaining", - "title": "Disallow use of optional chaining in contexts where the \`undefined\` value is not allowed", - "value": 0, - }, - { - "displayValue": "1 warning", - "docsUrl": "https://eslint.org/docs/latest/rules/no-unused-vars", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 0, - "slug": "no-unused-vars", - "title": "Disallow unused variables", - "value": 1, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/use-isnan", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "use-isnan", - "title": "Require calls to \`isNaN()\` when checking for \`NaN\`", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/valid-typeof", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "valid-typeof", - "title": "Enforce comparing \`typeof\` expressions against valid strings", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/camelcase", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "camelcase", - "title": "Enforce camelcase naming convention", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/curly", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "curly", - "title": "Enforce consistent brace style for all control statements", - "value": 0, - }, - { - "displayValue": "1 warning", - "docsUrl": "https://eslint.org/docs/latest/rules/eqeqeq", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 0, - "slug": "eqeqeq", - "title": "Require the use of \`===\` and \`!==\`", - "value": 1, - }, - { - "displayValue": "1 warning", - "docsUrl": "https://eslint.org/docs/latest/rules/max-lines-per-function", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 0, - "slug": "max-lines-per-function", - "title": "Enforce a maximum number of lines of code in a function", - "value": 1, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/max-lines", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "max-lines", - "title": "Enforce a maximum number of lines per file", - "value": 0, - }, - { - "displayValue": "3 warnings", - "docsUrl": "https://eslint.org/docs/latest/rules/no-shadow", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 0, - "slug": "no-shadow", - "title": "Disallow variable declarations from shadowing variables declared in the outer scope", - "value": 3, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/no-var", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "no-var", - "title": "Require \`let\` or \`const\` instead of \`var\`", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/prefer-arrow-callback", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "prefer-arrow-callback", - "title": "Require using arrow functions for callbacks", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/prefer-object-spread", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "prefer-object-spread", - "title": "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://eslint.org/docs/latest/rules/yoda", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "yoda", - "title": "Require or disallow "Yoda" conditions", - "value": 0, - }, - { - "displayValue": "1 warning", - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-key.md", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 0, - "slug": "react-jsx-key", - "title": "Disallow missing \`key\` props in iterators/collection literals", - "value": 1, - }, - { - "displayValue": "6 warnings", - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/prop-types.md", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 0, - "slug": "react-prop-types", - "title": "Disallow missing props validation in a React component definition", - "value": 6, - }, - { - "displayValue": "passed", - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/react-in-jsx-scope.md", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "react-react-in-jsx-scope", - "title": "Disallow missing React when using JSX", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-uses-vars.md", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "react-jsx-uses-vars", - "title": "Disallow variables used in JSX to be incorrectly marked as unused", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://github.com/jsx-eslint/eslint-plugin-react/tree/master/docs/rules/jsx-uses-react.md", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "react-jsx-uses-react", - "title": "Disallow React to be incorrectly marked as unused", - "value": 0, - }, - { - "displayValue": "passed", - "docsUrl": "https://reactjs.org/docs/hooks-rules.html", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 1, - "slug": "react-hooks-rules-of-hooks", - "title": "enforces the Rules of Hooks", - "value": 0, - }, - { - "displayValue": "2 warnings", - "docsUrl": "https://github.com/facebook/react/issues/14920", - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 0, - "slug": "react-hooks-exhaustive-deps", - "title": "verifies the list of dependencies for Hooks like useEffect and similar", - "value": 2, - }, - ], + "unchanged": [], }, "categories": { "added": [], "changed": [ { "scores": { - "after": 0.7692307692307693, - "before": 0.5384615384615384, - "diff": 0.23076923076923084, + "after": 0.7, + "before": 0.3, + "diff": 0.39999999999999997, }, - "slug": "code-style", - "title": "Code style", + "slug": "dummy-category", + "title": "Dummy Category", }, ], "removed": [], - "unchanged": [ - { - "score": 0.6842105263157895, - "slug": "bug-prevention", - "title": "Bug prevention", - }, - ], + "unchanged": [], }, "commits": Any, "date": Any, "duration": Any, "groups": { "added": [], - "changed": [ - { - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "scores": { - "after": 0.7142857142857143, - "before": 0.5, - "diff": 0.2142857142857143, - }, - "slug": "suggestions", - "title": "Suggestions", - }, - ], + "changed": [], "removed": [], - "unchanged": [ - { - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 0.9166666666666666, - "slug": "problems", - "title": "Problems", - }, - { - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 0.6666666666666666, - "slug": "react-best-practices", - "title": "Best Practices (react)", - }, - { - "plugin": { - "docsUrl": "https://www.npmjs.com/package/@code-pushup/eslint-plugin", - "slug": "eslint", - "title": "ESLint", - }, - "score": 0.5, - "slug": "react-possible-errors", - "title": "Possible Errors (react)", - }, - ], + "unchanged": [], }, "packageName": "@code-pushup/core", "version": Any, diff --git a/e2e/cli-e2e/tests/__snapshots__/compare.report-diff.md b/e2e/cli-e2e/tests/__snapshots__/compare.report-diff.md index acb2069df..73afdafaa 100644 --- a/e2e/cli-e2e/tests/__snapshots__/compare.report-diff.md +++ b/e2e/cli-e2e/tests/__snapshots__/compare.report-diff.md @@ -4,30 +4,17 @@ ## 🏷️ Categories -| 🏷️ Category | ⭐ Previous score | ⭐ Current score | πŸ”„ Score change | -| :------------- | :--------------: | :-------------: | :----------------------------------------------------------------: | -| Code style | 🟑 54 | 🟑 **77** | ![↑ +23.1](https://img.shields.io/badge/%E2%86%91%20%2B23.1-green) | -| Bug prevention | 🟑 68 | 🟑 **68** | – | +| 🏷️ Category | ⭐ Previous score | ⭐ Current score | πŸ”„ Score change | +| :------------- | :--------------: | :-------------: | :------------------------------------------------------------: | +| Dummy Category | πŸ”΄ 30 | 🟑 **70** | ![↑ +40](https://img.shields.io/badge/%E2%86%91%20%2B40-green) |
-πŸ‘ 1 group improved, πŸ‘ 3 audits improved - -## πŸ—ƒοΈ Groups - -| πŸ”Œ Plugin | πŸ—ƒοΈ Group | ⭐ Previous score | ⭐ Current score | πŸ”„ Score change | -| :----------------------------------------------------------------- | :---------- | :--------------: | :-------------: | :----------------------------------------------------------------: | -| [ESLint](https://www.npmjs.com/package/@code-pushup/eslint-plugin) | Suggestions | 🟑 50 | 🟑 **71** | ![↑ +21.4](https://img.shields.io/badge/%E2%86%91%20%2B21.4-green) | - -3 other groups are unchanged. +πŸ‘ 1 audit improved ## πŸ›‘οΈ Audits -| πŸ”Œ Plugin | πŸ›‘οΈ Audit | πŸ“ Previous value | πŸ“ Current value | πŸ”„ Value change | -| :----------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | :---------------: | :--------------: | :----------------------------------------------------------------------------------: | -| [ESLint](https://www.npmjs.com/package/@code-pushup/eslint-plugin) | [Require or disallow method and property shorthand syntax for object literals](https://eslint.org/docs/latest/rules/object-shorthand) | πŸŸ₯ 3 warnings | 🟩 **passed** | ![↓ βˆ’100 %](https://img.shields.io/badge/%E2%86%93%20%E2%88%92100%E2%80%89%25-green) | -| [ESLint](https://www.npmjs.com/package/@code-pushup/eslint-plugin) | [Require braces around arrow function bodies](https://eslint.org/docs/latest/rules/arrow-body-style) | πŸŸ₯ 1 warning | 🟩 **passed** | ![↓ βˆ’100 %](https://img.shields.io/badge/%E2%86%93%20%E2%88%92100%E2%80%89%25-green) | -| [ESLint](https://www.npmjs.com/package/@code-pushup/eslint-plugin) | [Require `const` declarations for variables that are never reassigned after declared](https://eslint.org/docs/latest/rules/prefer-const) | πŸŸ₯ 1 warning | 🟩 **passed** | ![↓ βˆ’100 %](https://img.shields.io/badge/%E2%86%93%20%E2%88%92100%E2%80%89%25-green) | - -28 other audits are unchanged. +| πŸ”Œ Plugin | πŸ›‘οΈ Audit | πŸ“ Previous value | πŸ“ Current value | πŸ”„ Value change | +| :----------- | :---------- | :---------------: | :--------------: | :--------------------------------------------------------------------------------: | +| Dummy Plugin | Dummy Audit | πŸŸ₯ 3 | 🟨 **7** | ![↑ +133.3 %](https://img.shields.io/badge/%E2%86%91%20%2B133.3%E2%80%89%25-green) |
diff --git a/e2e/cli-e2e/tests/collect.e2e.test.ts b/e2e/cli-e2e/tests/collect.e2e.test.ts index 912068b58..707ae96bc 100644 --- a/e2e/cli-e2e/tests/collect.e2e.test.ts +++ b/e2e/cli-e2e/tests/collect.e2e.test.ts @@ -1,69 +1,69 @@ -import type { AuditReport, PluginReport, Report } from '@code-pushup/models'; -import { cleanTestFolder } from '@code-pushup/test-setup'; +import { cp } from 'node:fs/promises'; +import { join } from 'node:path'; +import { afterEach, beforeAll, describe, expect, it } from 'vitest'; +import { nxTargetProject } from '@code-pushup/test-nx-utils'; +import { teardownTestFolder } from '@code-pushup/test-setup'; +import { E2E_ENVIRONMENTS_DIR, TEST_OUTPUT_DIR } from '@code-pushup/test-utils'; import { executeProcess, readTextFile } from '@code-pushup/utils'; describe('CLI collect', () => { - const exampleCategoryTitle = 'Code style'; - const exampleAuditTitle = 'Disallow unused variables'; + const dummyPluginTitle = 'Dummy Plugin'; + const dummyAuditTitle = 'Dummy Audit'; + const fixtureDummyDir = join( + 'e2e', + nxTargetProject(), + 'mocks', + 'fixtures', + 'dummy-setup', + ); + const testFileDir = join( + E2E_ENVIRONMENTS_DIR, + nxTargetProject(), + TEST_OUTPUT_DIR, + 'collect', + ); + const dummyDir = join(testFileDir, 'dummy-setup'); + const dummyOutputDir = join(dummyDir, '.code-pushup'); - /* eslint-disable @typescript-eslint/no-unused-vars */ - const omitVariableAuditData = ({ - score, - value, - displayValue, - ...auditReport - }: AuditReport) => auditReport; - const omitVariablePluginData = ({ - date, - duration, - version, - audits, - ...pluginReport - }: PluginReport) => - ({ - ...pluginReport, - audits: audits.map( - pluginReport.slug === 'lighthouse' ? omitVariableAuditData : p => p, - ) as AuditReport[], - }) as PluginReport; - const omitVariableReportData = ({ - commit, - date, - duration, - version, - ...report - }: Report) => ({ - ...report, - plugins: report.plugins.map(omitVariablePluginData), + beforeAll(async () => { + await cp(fixtureDummyDir, dummyDir, { recursive: true }); }); - /* eslint-enable @typescript-eslint/no-unused-vars */ - beforeEach(async () => { - await cleanTestFolder('tmp/e2e/react-todos-app'); + afterAll(async () => { + await teardownTestFolder(dummyDir); + }); + + afterEach(async () => { + await teardownTestFolder(dummyOutputDir); }); it('should create report.md', async () => { const { code, stderr } = await executeProcess({ - command: 'code-pushup', - args: ['collect', '--persist.format=md', '--no-progress'], - cwd: 'examples/react-todos-app', + command: 'npx', + args: [ + '@code-pushup/cli', + '--no-progress', + 'collect', + '--persist.format=md', + ], + cwd: dummyDir, }); expect(code).toBe(0); expect(stderr).toBe(''); - const md = await readTextFile('tmp/e2e/react-todos-app/report.md'); + const md = await readTextFile(join(dummyOutputDir, 'report.md')); expect(md).toContain('# Code PushUp Report'); - expect(md).toContain(exampleCategoryTitle); - expect(md).toContain(exampleAuditTitle); + expect(md).toContain(dummyPluginTitle); + expect(md).toContain(dummyAuditTitle); }); it('should print report summary to stdout', async () => { const { code, stdout, stderr } = await executeProcess({ - command: 'code-pushup', - args: ['collect', '--no-progress'], - cwd: 'examples/react-todos-app', + command: 'npx', + args: ['@code-pushup/cli', '--no-progress', 'collect'], + cwd: dummyDir, }); expect(code).toBe(0); @@ -71,7 +71,7 @@ describe('CLI collect', () => { expect(stdout).toContain('Code PushUp Report'); expect(stdout).not.toContain('Generated reports'); - expect(stdout).toContain(exampleCategoryTitle); - expect(stdout).toContain(exampleAuditTitle); + expect(stdout).toContain(dummyPluginTitle); + expect(stdout).toContain(dummyAuditTitle); }); }); diff --git a/e2e/cli-e2e/tests/compare.e2e.test.ts b/e2e/cli-e2e/tests/compare.e2e.test.ts index 9f5744c9f..73d08b6b2 100644 --- a/e2e/cli-e2e/tests/compare.e2e.test.ts +++ b/e2e/cli-e2e/tests/compare.e2e.test.ts @@ -1,61 +1,52 @@ -import { simpleGit } from 'simple-git'; +import { cp } from 'node:fs/promises'; +import { join } from 'node:path'; +import { beforeAll } from 'vitest'; import type { ReportsDiff } from '@code-pushup/models'; -import { cleanTestFolder } from '@code-pushup/test-setup'; +import { nxTargetProject } from '@code-pushup/test-nx-utils'; +import { teardownTestFolder } from '@code-pushup/test-setup'; +import { E2E_ENVIRONMENTS_DIR, TEST_OUTPUT_DIR } from '@code-pushup/test-utils'; import { executeProcess, readJsonFile, readTextFile } from '@code-pushup/utils'; describe('CLI compare', () => { - const git = simpleGit(); + const fixtureDummyDir = join( + 'e2e', + nxTargetProject(), + 'mocks', + 'fixtures', + 'existing-reports', + ); - beforeEach(async () => { - if (await git.diff(['--', 'examples/react-todos-app'])) { - throw new Error( - 'Unstaged changes found in examples/react-todos-app, please stage or commit them to prevent E2E tests interfering', - ); - } - await cleanTestFolder('tmp/e2e/react-todos-app'); - await executeProcess({ - command: 'code-pushup', - args: [ - 'collect', - '--persist.filename=source-report', - '--onlyPlugins=eslint', - ], - cwd: 'examples/react-todos-app', - }); - await executeProcess({ - command: 'npx', - args: ['eslint', '--fix', 'src', '--ext=js,jsx'], - cwd: 'examples/react-todos-app', - }); - await executeProcess({ - command: 'code-pushup', - args: [ - 'collect', - '--persist.filename=target-report', - '--onlyPlugins=eslint', - ], - cwd: 'examples/react-todos-app', - }); - }, 20_000); + const testFileDir = join( + E2E_ENVIRONMENTS_DIR, + nxTargetProject(), + TEST_OUTPUT_DIR, + 'compare', + ); + const existingDir = join(testFileDir, 'existing-reports'); + const existingOutputDir = join(existingDir, '.code-pushup'); - afterEach(async () => { - await git.checkout(['--', 'examples/react-todos-app']); - await cleanTestFolder('tmp/e2e'); + beforeAll(async () => { + await cp(fixtureDummyDir, existingDir, { recursive: true }); + }); + + afterAll(async () => { + await teardownTestFolder(existingDir); }); it('should compare report.json files and create report-diff.json and report-diff.md', async () => { await executeProcess({ - command: 'code-pushup', + command: 'npx', args: [ + '@code-pushup/cli', 'compare', - '--before=../../tmp/e2e/react-todos-app/source-report.json', - '--after=../../tmp/e2e/react-todos-app/target-report.json', + `--before=${join('.code-pushup', 'source-report.json')}`, + `--after=${join('.code-pushup', 'target-report.json')}`, ], - cwd: 'examples/react-todos-app', + cwd: existingDir, }); const reportsDiff = await readJsonFile( - 'tmp/e2e/react-todos-app/report-diff.json', + join(existingOutputDir, 'report-diff.json'), ); expect(reportsDiff).toMatchSnapshot({ commits: expect.any(Object), @@ -65,7 +56,7 @@ describe('CLI compare', () => { }); const reportsDiffMd = await readTextFile( - 'tmp/e2e/react-todos-app/report-diff.md', + join(existingOutputDir, 'report-diff.md'), ); // commits are variable, replace SHAs with placeholders const sanitizedMd = reportsDiffMd.replace(/[\da-f]{40}/g, '``'); diff --git a/e2e/cli-e2e/tests/help.e2e.test.ts b/e2e/cli-e2e/tests/help.e2e.test.ts index cf5316be1..ec2390c98 100644 --- a/e2e/cli-e2e/tests/help.e2e.test.ts +++ b/e2e/cli-e2e/tests/help.e2e.test.ts @@ -1,11 +1,19 @@ -import { removeColorCodes } from '@code-pushup/test-utils'; +import { join } from 'node:path'; +import { nxTargetProject } from '@code-pushup/test-nx-utils'; +import { + E2E_ENVIRONMENTS_DIR, + removeColorCodes, +} from '@code-pushup/test-utils'; import { executeProcess } from '@code-pushup/utils'; describe('CLI help', () => { + const envRoot = join(E2E_ENVIRONMENTS_DIR, nxTargetProject()); + it('should print help with help command', async () => { const { code, stdout, stderr } = await executeProcess({ - command: 'code-pushup', - args: ['help'], + command: 'npx', + args: ['@code-pushup/cli', 'help'], + cwd: envRoot, }); expect(code).toBe(0); expect(stderr).toBe(''); @@ -14,12 +22,13 @@ describe('CLI help', () => { it('should produce the same output to stdout for both help argument and help command', async () => { const helpArgResult = await executeProcess({ - command: 'code-pushup', - args: ['help'], + command: 'npx', + args: ['@code-pushup/cli', 'help'], }); const helpCommandResult = await executeProcess({ - command: 'code-pushup', - args: ['--help'], + command: 'npx', + args: ['@code-pushup/cli', '--help'], + cwd: envRoot, }); expect(helpArgResult.code).toBe(0); expect(helpCommandResult.code).toBe(0); diff --git a/e2e/cli-e2e/tests/print-config.e2e.test.ts b/e2e/cli-e2e/tests/print-config.e2e.test.ts index 46190659d..7f8a06ae1 100644 --- a/e2e/cli-e2e/tests/print-config.e2e.test.ts +++ b/e2e/cli-e2e/tests/print-config.e2e.test.ts @@ -1,18 +1,46 @@ +import { cp } from 'node:fs/promises'; import { join } from 'node:path'; -import { expect } from 'vitest'; +import { beforeAll, expect } from 'vitest'; +import { nxTargetProject } from '@code-pushup/test-nx-utils'; +import { teardownTestFolder } from '@code-pushup/test-setup'; +import { E2E_ENVIRONMENTS_DIR, TEST_OUTPUT_DIR } from '@code-pushup/test-utils'; import { executeProcess } from '@code-pushup/utils'; -const extensions = ['js', 'mjs', 'ts'] as const; -export const configFilePath = (ext: (typeof extensions)[number]) => - join(process.cwd(), `e2e/cli-e2e/mocks/fixtures/code-pushup.config.${ext}`); +describe('CLI print-config', () => { + const extensions = ['js', 'mjs', 'ts'] as const; + const fixtureDummyDir = join( + 'e2e', + nxTargetProject(), + 'mocks', + 'fixtures', + 'dummy-setup', + ); + + const testFileDir = join( + E2E_ENVIRONMENTS_DIR, + nxTargetProject(), + TEST_OUTPUT_DIR, + 'print-config', + ); + const testFileDummySetup = join(testFileDir, 'dummy-setup'); + const configFilePath = (ext: (typeof extensions)[number]) => + join(process.cwd(), testFileDummySetup, `code-pushup.config.${ext}`); + + beforeAll(async () => { + await cp(fixtureDummyDir, testFileDummySetup, { recursive: true }); + }); + + afterAll(async () => { + await teardownTestFolder(testFileDummySetup); + }); -describe('print-config', () => { it.each(extensions)( 'should load .%s config file with correct arguments', async ext => { const { code, stdout } = await executeProcess({ - command: 'code-pushup', + command: 'npx', args: [ + '@code-pushup/cli', 'print-config', '--no-progress', `--config=${configFilePath(ext)}`, @@ -20,8 +48,8 @@ describe('print-config', () => { '--persist.outputDir=output-dir', '--persist.format=md', `--persist.filename=${ext}-report`, - '--onlyPlugins=coverage', ], + cwd: testFileDummySetup, }); expect(code).toBe(0); @@ -30,26 +58,13 @@ describe('print-config', () => { expect.objectContaining({ config: expect.stringContaining(`code-pushup.config.${ext}`), tsconfig: 'tsconfig.base.json', - // filled by command options - persist: { - outputDir: 'output-dir', - filename: `${ext}-report`, - format: ['md'], - }, - upload: { - organization: 'code-pushup', - project: `cli-${ext}`, - apiKey: 'e2e-api-key', - server: 'https://e2e.com/api', - }, plugins: [ expect.objectContaining({ - slug: 'coverage', - title: 'Code coverage', + slug: 'dummy-plugin', + title: 'Dummy Plugin', }), ], - categories: [expect.objectContaining({ slug: 'code-coverage' })], - onlyPlugins: ['coverage'], + categories: [expect.objectContaining({ slug: 'dummy-category' })], }), ); }, diff --git a/e2e/cli-e2e/vite.config.e2e.ts b/e2e/cli-e2e/vite.config.e2e.ts index f1b3c3b93..2514c0209 100644 --- a/e2e/cli-e2e/vite.config.e2e.ts +++ b/e2e/cli-e2e/vite.config.e2e.ts @@ -16,7 +16,6 @@ export default defineConfig({ }, environment: 'node', include: ['tests/**/*.e2e.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - globalSetup: ['../../global-setup.e2e.ts'], setupFiles: ['../../testing/test-setup/src/lib/reset.mocks.ts'], }, }); diff --git a/e2e/create-cli-e2e/project.json b/e2e/create-cli-e2e/project.json index c216582b8..ecb0d138f 100644 --- a/e2e/create-cli-e2e/project.json +++ b/e2e/create-cli-e2e/project.json @@ -18,13 +18,6 @@ } } }, - "implicitDependencies": [ - "models", - "utils", - "core", - "cli", - "nx-plugin", - "create-cli" - ], + "implicitDependencies": ["create-cli"], "tags": ["scope:tooling", "type:e2e"] } diff --git a/e2e/create-cli-e2e/tests/init.e2e.test.ts b/e2e/create-cli-e2e/tests/init.e2e.test.ts index de5dd73e8..9077d9b49 100644 --- a/e2e/create-cli-e2e/tests/init.e2e.test.ts +++ b/e2e/create-cli-e2e/tests/init.e2e.test.ts @@ -1,29 +1,29 @@ -import { dirname, join, relative } from 'node:path'; +import { join } from 'node:path'; import { afterEach, expect } from 'vitest'; +import { nxTargetProject } from '@code-pushup/test-nx-utils'; import { teardownTestFolder } from '@code-pushup/test-setup'; -import { createNpmWorkspace, removeColorCodes } from '@code-pushup/test-utils'; +import { + E2E_ENVIRONMENTS_DIR, + TEST_OUTPUT_DIR, + createNpmWorkspace, + removeColorCodes, +} from '@code-pushup/test-utils'; import { executeProcess, readJsonFile, readTextFile } from '@code-pushup/utils'; describe('create-cli-inti', () => { - const workspaceRoot = 'tmp/e2e/create-cli-e2e'; - const baseDir = 'tmp/e2e/create-cli-e2e/__test__/init'; + const workspaceRoot = join(E2E_ENVIRONMENTS_DIR, nxTargetProject()); + const testFileDir = join(workspaceRoot, TEST_OUTPUT_DIR, 'init'); afterEach(async () => { - await teardownTestFolder(baseDir); + await teardownTestFolder(testFileDir); }); it('should execute package correctly over npm exec', async () => { - const cwd = join(baseDir, 'npm-exec'); - const userconfig = relative(cwd, join(workspaceRoot, '.npmrc')); + const cwd = join(testFileDir, 'npm-exec'); await createNpmWorkspace(cwd); const { code, stdout } = await executeProcess({ command: 'npm', - args: [ - 'exec', - '@code-pushup/create-cli', - `--userconfig=${userconfig}`, - `--prefix=${dirname(userconfig)}`, - ], + args: ['exec', '@code-pushup/create-cli'], cwd, }); @@ -53,19 +53,12 @@ describe('create-cli-inti', () => { }); it('should execute package correctly over npm init', async () => { - const cwd = join(baseDir, 'npm-init'); - const userconfig = relative(cwd, join(workspaceRoot, '.npmrc')); - + const cwd = join(testFileDir, 'npm-init-setup'); await createNpmWorkspace(cwd); const { code, stdout } = await executeProcess({ command: 'npm', - args: [ - 'init', - '@code-pushup/cli', - `--userconfig=${userconfig}`, - `--prefix=${dirname(userconfig)}`, - ], + args: ['init', '@code-pushup/cli'], cwd, }); @@ -95,26 +88,19 @@ describe('create-cli-inti', () => { }); it('should produce an executable setup when running npm init', async () => { - const cwd = join(baseDir, 'npm-init-executable'); - const userconfig = relative(cwd, join(workspaceRoot, '.npmrc')); - + const cwd = join(testFileDir, 'npm-init-executable'); await createNpmWorkspace(cwd); await executeProcess({ command: 'npm', - args: [ - 'init', - '@code-pushup/cli', - `--userconfig=${userconfig}`, - `--prefix=${dirname(userconfig)}`, - ], + args: ['init', '@code-pushup/cli'], cwd, }); await expect( executeProcess({ command: 'npx', - args: ['@code-pushup/cli print-config', `--userconfig=${userconfig}`], + args: ['@code-pushup/cli print-config'], cwd, }), ) diff --git a/e2e/nx-plugin-e2e/tests/executor-cli.e2e.test.ts b/e2e/nx-plugin-e2e/tests/executor-cli.e2e.test.ts index 6fbb81188..dadac76ac 100644 --- a/e2e/nx-plugin-e2e/tests/executor-cli.e2e.test.ts +++ b/e2e/nx-plugin-e2e/tests/executor-cli.e2e.test.ts @@ -6,9 +6,15 @@ import { generateCodePushupConfig } from '@code-pushup/nx-plugin'; import { generateWorkspaceAndProject, materializeTree, + nxTargetProject, } from '@code-pushup/test-nx-utils'; import { teardownTestFolder } from '@code-pushup/test-setup'; -import { osAgnosticPath, removeColorCodes } from '@code-pushup/test-utils'; +import { + E2E_ENVIRONMENTS_DIR, + TEST_OUTPUT_DIR, + osAgnosticPath, + removeColorCodes, +} from '@code-pushup/test-utils'; import { executeProcess, readJsonFile } from '@code-pushup/utils'; function relativePathToCwd(testDir: string): string { @@ -54,18 +60,23 @@ async function addTargetToWorkspace( describe('executor command', () => { let tree: Tree; const project = 'my-lib'; - const baseDir = 'tmp/e2e/nx-plugin-e2e/__test__/executor/cli'; + const testFileDir = join( + E2E_ENVIRONMENTS_DIR, + nxTargetProject(), + TEST_OUTPUT_DIR, + 'executor-cli', + ); beforeEach(async () => { tree = await generateWorkspaceAndProject(project); }); afterEach(async () => { - await teardownTestFolder(baseDir); + await teardownTestFolder(testFileDir); }); it('should execute no specific command by default', async () => { - const cwd = join(baseDir, 'execute-default-command'); + const cwd = join(testFileDir, 'execute-default-command'); await addTargetToWorkspace(tree, { cwd, project }); const { stdout, code } = await executeProcess({ command: 'npx', @@ -79,7 +90,7 @@ describe('executor command', () => { }); it('should execute print-config executor', async () => { - const cwd = join(baseDir, 'execute-print-config-command'); + const cwd = join(testFileDir, 'execute-print-config-command'); await addTargetToWorkspace(tree, { cwd, project }); const { stdout, code } = await executeProcess({ @@ -98,7 +109,7 @@ describe('executor command', () => { }); it('should execute collect executor and add report to sub folder named by project', async () => { - const cwd = join(baseDir, 'execute-collect-command'); + const cwd = join(testFileDir, 'execute-collect-command'); await addTargetToWorkspace(tree, { cwd, project }); const { stdout, code } = await executeProcess({ diff --git a/e2e/nx-plugin-e2e/tests/generator-configuration.e2e.test.ts b/e2e/nx-plugin-e2e/tests/generator-configuration.e2e.test.ts index bff0ec4b2..4ad65594f 100644 --- a/e2e/nx-plugin-e2e/tests/generator-configuration.e2e.test.ts +++ b/e2e/nx-plugin-e2e/tests/generator-configuration.e2e.test.ts @@ -6,27 +6,37 @@ import { generateCodePushupConfig } from '@code-pushup/nx-plugin'; import { generateWorkspaceAndProject, materializeTree, + nxTargetProject, } from '@code-pushup/test-nx-utils'; import { teardownTestFolder } from '@code-pushup/test-setup'; -import { removeColorCodes } from '@code-pushup/test-utils'; +import { + E2E_ENVIRONMENTS_DIR, + TEST_OUTPUT_DIR, + removeColorCodes, +} from '@code-pushup/test-utils'; import { executeProcess } from '@code-pushup/utils'; describe('nx-plugin g configuration', () => { let tree: Tree; const project = 'my-lib'; const projectRoot = join('libs', project); - const baseDir = 'tmp/e2e/nx-plugin-e2e/__test__/generators/configuration'; + const testFileDir = join( + E2E_ENVIRONMENTS_DIR, + nxTargetProject(), + TEST_OUTPUT_DIR, + 'generators-configuration', + ); beforeEach(async () => { tree = await generateWorkspaceAndProject(project); }); afterEach(async () => { - await teardownTestFolder(baseDir); + await teardownTestFolder(testFileDir); }); it('should generate code-pushup.config.ts file and add target to project.json', async () => { - const cwd = join(baseDir, 'configure'); + const cwd = join(testFileDir, 'configure'); await materializeTree(tree, cwd); const { code, stdout, stderr } = await executeProcess({ @@ -76,7 +86,7 @@ describe('nx-plugin g configuration', () => { }); it('should NOT create a code-pushup.config.ts file if one already exists', async () => { - const cwd = join(baseDir, 'configure-config-existing'); + const cwd = join(testFileDir, 'configure-config-existing'); generateCodePushupConfig(tree, projectRoot); await materializeTree(tree, cwd); @@ -116,7 +126,7 @@ describe('nx-plugin g configuration', () => { }); it('should NOT create a code-pushup.config.ts file if skipConfig is given', async () => { - const cwd = join(baseDir, 'configure-skip-config'); + const cwd = join(testFileDir, 'configure-skip-config'); await materializeTree(tree, cwd); const { code, stdout } = await executeProcess({ @@ -161,7 +171,7 @@ describe('nx-plugin g configuration', () => { }); it('should NOT add target to project.json if skipTarget is given', async () => { - const cwd = join(baseDir, 'configure-skip-target'); + const cwd = join(testFileDir, 'configure-skip-target'); await materializeTree(tree, cwd); const { code, stdout } = await executeProcess({ @@ -205,7 +215,7 @@ describe('nx-plugin g configuration', () => { }); it('should inform about dry run', async () => { - const cwd = join(baseDir, 'configure'); + const cwd = join(testFileDir, 'configure'); await materializeTree(tree, cwd); const { stderr } = await executeProcess({ diff --git a/e2e/nx-plugin-e2e/tests/generator-init.e2e.test.ts b/e2e/nx-plugin-e2e/tests/generator-init.e2e.test.ts index 9a46080fe..4070fd4d5 100644 --- a/e2e/nx-plugin-e2e/tests/generator-init.e2e.test.ts +++ b/e2e/nx-plugin-e2e/tests/generator-init.e2e.test.ts @@ -5,26 +5,36 @@ import { afterEach, expect } from 'vitest'; import { generateWorkspaceAndProject, materializeTree, + nxTargetProject, } from '@code-pushup/test-nx-utils'; import { teardownTestFolder } from '@code-pushup/test-setup'; -import { removeColorCodes } from '@code-pushup/test-utils'; +import { + E2E_ENVIRONMENTS_DIR, + TEST_OUTPUT_DIR, + removeColorCodes, +} from '@code-pushup/test-utils'; import { executeProcess } from '@code-pushup/utils'; describe('nx-plugin g init', () => { let tree: Tree; const project = 'my-lib'; - const baseDir = 'tmp/e2e/nx-plugin-e2e/__test__/generators/init'; + const testFileDir = join( + E2E_ENVIRONMENTS_DIR, + nxTargetProject(), + TEST_OUTPUT_DIR, + 'generators-init', + ); beforeEach(async () => { tree = await generateWorkspaceAndProject(project); }); afterEach(async () => { - await teardownTestFolder(baseDir); + await teardownTestFolder(testFileDir); }); it('should inform about dry run when used on init generator', async () => { - const cwd = join(baseDir, 'dry-run'); + const cwd = join(testFileDir, 'dry-run'); await materializeTree(tree, cwd); const { stderr } = await executeProcess({ @@ -40,7 +50,7 @@ describe('nx-plugin g init', () => { }); it('should update packages.json and configure nx.json', async () => { - const cwd = join(baseDir, 'nx-update'); + const cwd = join(testFileDir, 'nx-update'); await materializeTree(tree, cwd); const { code, stdout } = await executeProcess({ @@ -89,7 +99,7 @@ describe('nx-plugin g init', () => { }); it('should skip packages.json update if --skipPackageJson is given', async () => { - const cwd = join(baseDir, 'skip-packages'); + const cwd = join(testFileDir, 'skip-packages'); await materializeTree(tree, cwd); const { code, stdout } = await executeProcess({ diff --git a/e2e/nx-plugin-e2e/tests/plugin-create-nodes.e2e.test.ts b/e2e/nx-plugin-e2e/tests/plugin-create-nodes.e2e.test.ts index 2616ec1da..163ba1a11 100644 --- a/e2e/nx-plugin-e2e/tests/plugin-create-nodes.e2e.test.ts +++ b/e2e/nx-plugin-e2e/tests/plugin-create-nodes.e2e.test.ts @@ -7,28 +7,38 @@ import { generateWorkspaceAndProject, materializeTree, nxShowProjectJson, + nxTargetProject, registerPluginInWorkspace, } from '@code-pushup/test-nx-utils'; import { teardownTestFolder } from '@code-pushup/test-setup'; -import { removeColorCodes } from '@code-pushup/test-utils'; +import { + E2E_ENVIRONMENTS_DIR, + TEST_OUTPUT_DIR, + removeColorCodes, +} from '@code-pushup/test-utils'; import { executeProcess, readTextFile } from '@code-pushup/utils'; describe('nx-plugin', () => { let tree: Tree; const project = 'my-lib'; const projectRoot = join('libs', project); - const baseDir = 'tmp/e2e/nx-plugin-e2e/__test__/plugin/create-nodes'; + const testFileDir = join( + E2E_ENVIRONMENTS_DIR, + nxTargetProject(), + TEST_OUTPUT_DIR, + 'plugin-create-nodes', + ); beforeEach(async () => { tree = await generateWorkspaceAndProject(project); }); afterEach(async () => { - await teardownTestFolder(baseDir); + await teardownTestFolder(testFileDir); }); it('should add configuration target dynamically', async () => { - const cwd = join(baseDir, 'add-configuration-dynamically'); + const cwd = join(testFileDir, 'add-configuration-dynamically'); registerPluginInWorkspace(tree, '@code-pushup/nx-plugin'); await materializeTree(tree, cwd); @@ -49,7 +59,7 @@ describe('nx-plugin', () => { }); it('should execute dynamic configuration target', async () => { - const cwd = join(baseDir, 'execute-dynamic-configuration'); + const cwd = join(testFileDir, 'execute-dynamic-configuration'); registerPluginInWorkspace(tree, { plugin: '@code-pushup/nx-plugin', }); @@ -73,7 +83,7 @@ describe('nx-plugin', () => { }); it('should consider plugin option targetName in configuration target', async () => { - const cwd = join(baseDir, 'configuration-option-target-name'); + const cwd = join(testFileDir, 'configuration-option-target-name'); registerPluginInWorkspace(tree, { plugin: '@code-pushup/nx-plugin', options: { @@ -92,7 +102,7 @@ describe('nx-plugin', () => { }); it('should consider plugin option bin in configuration target', async () => { - const cwd = join(baseDir, 'configuration-option-bin'); + const cwd = join(testFileDir, 'configuration-option-bin'); registerPluginInWorkspace(tree, { plugin: '@code-pushup/nx-plugin', options: { @@ -115,7 +125,7 @@ describe('nx-plugin', () => { }); it('should NOT add config targets dynamically if the project is configured', async () => { - const cwd = join(baseDir, 'configuration-already-configured'); + const cwd = join(testFileDir, 'configuration-already-configured'); registerPluginInWorkspace(tree, '@code-pushup/nx-plugin'); const { root } = readProjectConfiguration(tree, project); generateCodePushupConfig(tree, root); @@ -134,7 +144,7 @@ describe('nx-plugin', () => { }); it('should add executor target dynamically if the project is configured', async () => { - const cwd = join(baseDir, 'add-executor-dynamically'); + const cwd = join(testFileDir, 'add-executor-dynamically'); registerPluginInWorkspace(tree, '@code-pushup/nx-plugin'); const { root } = readProjectConfiguration(tree, project); generateCodePushupConfig(tree, root); @@ -155,7 +165,7 @@ describe('nx-plugin', () => { }); it('should execute dynamic executor target', async () => { - const cwd = join(baseDir, 'execute-dynamic-executor'); + const cwd = join(testFileDir, 'execute-dynamic-executor'); const pathRelativeToPackage = relative(join(cwd, 'libs', project), cwd); registerPluginInWorkspace(tree, { plugin: '@code-pushup/nx-plugin', @@ -200,7 +210,7 @@ describe('nx-plugin', () => { }); it('should consider plugin option bin in executor target', async () => { - const cwd = join(baseDir, 'configuration-option-bin'); + const cwd = join(testFileDir, 'configuration-option-bin'); registerPluginInWorkspace(tree, { plugin: '@code-pushup/nx-plugin', options: { @@ -223,7 +233,7 @@ describe('nx-plugin', () => { }); it('should consider plugin option projectPrefix in executor target', async () => { - const cwd = join(baseDir, 'configuration-option-bin'); + const cwd = join(testFileDir, 'configuration-option-bin'); registerPluginInWorkspace(tree, { plugin: '@code-pushup/nx-plugin', options: { @@ -249,7 +259,7 @@ describe('nx-plugin', () => { }); it('should NOT add targets dynamically if plugin is not registered', async () => { - const cwd = join(baseDir, 'plugin-not-registered'); + const cwd = join(testFileDir, 'plugin-not-registered'); await materializeTree(tree, cwd); const { code, projectJson } = await nxShowProjectJson(cwd, project); diff --git a/e2e/plugin-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap b/e2e/plugin-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap index d15a8626b..bc4f67f68 100644 --- a/e2e/plugin-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap +++ b/e2e/plugin-coverage-e2e/tests/__snapshots__/collect.e2e.test.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`collect report with coverage-plugin NPM package > should run Code coverage plugin that runs coverage tool and creates report.json 1`] = ` +exports[`PLUGIN collect report with coverage-plugin NPM package > should run Code coverage plugin that runs coverage tool and creates report.json 1`] = ` { "categories": [ { @@ -28,7 +28,7 @@ exports[`collect report with coverage-plugin NPM package > should run Code cover "message": "Function formatReportScore is not called in any test case.", "severity": "error", "source": { - "file": "tmp/e2e/plugin-coverage-e2e/existing-report/src/lib/partly-covered/utils.ts", + "file": "tmp/e2e/plugin-coverage-e2e/__test__/collect/existing-report/src/lib/partly-covered/utils.ts", "position": { "startLine": 2, }, @@ -38,7 +38,7 @@ exports[`collect report with coverage-plugin NPM package > should run Code cover "message": "Function sortReport is not called in any test case.", "severity": "error", "source": { - "file": "tmp/e2e/plugin-coverage-e2e/existing-report/src/lib/not-covered/sorting.ts", + "file": "tmp/e2e/plugin-coverage-e2e/__test__/collect/existing-report/src/lib/not-covered/sorting.ts", "position": { "startLine": 1, }, @@ -60,7 +60,7 @@ exports[`collect report with coverage-plugin NPM package > should run Code cover "message": "2nd branch is not taken in any test case.", "severity": "error", "source": { - "file": "tmp/e2e/plugin-coverage-e2e/existing-report/src/lib/partly-covered/utils.ts", + "file": "tmp/e2e/plugin-coverage-e2e/__test__/collect/existing-report/src/lib/partly-covered/utils.ts", "position": { "startLine": 6, }, @@ -70,7 +70,7 @@ exports[`collect report with coverage-plugin NPM package > should run Code cover "message": "2nd branch is not taken in any test case.", "severity": "error", "source": { - "file": "tmp/e2e/plugin-coverage-e2e/existing-report/src/lib/partly-covered/utils.ts", + "file": "tmp/e2e/plugin-coverage-e2e/__test__/collect/existing-report/src/lib/partly-covered/utils.ts", "position": { "startLine": 10, }, @@ -80,7 +80,7 @@ exports[`collect report with coverage-plugin NPM package > should run Code cover "message": "1st branch is not taken in any test case.", "severity": "error", "source": { - "file": "tmp/e2e/plugin-coverage-e2e/existing-report/src/lib/not-covered/sorting.ts", + "file": "tmp/e2e/plugin-coverage-e2e/__test__/collect/existing-report/src/lib/not-covered/sorting.ts", "position": { "startLine": 7, }, @@ -90,7 +90,7 @@ exports[`collect report with coverage-plugin NPM package > should run Code cover "message": "2nd branch is not taken in any test case.", "severity": "error", "source": { - "file": "tmp/e2e/plugin-coverage-e2e/existing-report/src/lib/not-covered/sorting.ts", + "file": "tmp/e2e/plugin-coverage-e2e/__test__/collect/existing-report/src/lib/not-covered/sorting.ts", "position": { "startLine": 7, }, @@ -112,7 +112,7 @@ exports[`collect report with coverage-plugin NPM package > should run Code cover "message": "Lines 7-9 are not covered in any test case.", "severity": "warning", "source": { - "file": "tmp/e2e/plugin-coverage-e2e/existing-report/src/lib/partly-covered/utils.ts", + "file": "tmp/e2e/plugin-coverage-e2e/__test__/collect/existing-report/src/lib/partly-covered/utils.ts", "position": { "endLine": 9, "startLine": 7, @@ -123,7 +123,7 @@ exports[`collect report with coverage-plugin NPM package > should run Code cover "message": "Lines 1-5 are not covered in any test case.", "severity": "warning", "source": { - "file": "tmp/e2e/plugin-coverage-e2e/existing-report/src/lib/not-covered/sorting.ts", + "file": "tmp/e2e/plugin-coverage-e2e/__test__/collect/existing-report/src/lib/not-covered/sorting.ts", "position": { "endLine": 5, "startLine": 1, @@ -171,7 +171,7 @@ exports[`collect report with coverage-plugin NPM package > should run Code cover } `; -exports[`collect report with coverage-plugin NPM package > should run Code coverage plugin which collects passed results and creates report.json 1`] = ` +exports[`PLUGIN collect report with coverage-plugin NPM package > should run Code coverage plugin which collects passed results and creates report.json 1`] = ` { "categories": [ { @@ -199,7 +199,7 @@ exports[`collect report with coverage-plugin NPM package > should run Code cover "message": "Function untested is not called in any test case.", "severity": "error", "source": { - "file": "tmp/e2e/plugin-coverage-e2e/basic-setup/src/index.mjs", + "file": "tmp/e2e/plugin-coverage-e2e/__test__/collect/basic-setup/src/index.mjs", "position": { "startLine": 1, }, @@ -221,7 +221,7 @@ exports[`collect report with coverage-plugin NPM package > should run Code cover "message": "1st branch is not taken in any test case.", "severity": "error", "source": { - "file": "tmp/e2e/plugin-coverage-e2e/basic-setup/src/index.mjs", + "file": "tmp/e2e/plugin-coverage-e2e/__test__/collect/basic-setup/src/index.mjs", "position": { "startLine": 10, }, @@ -243,7 +243,7 @@ exports[`collect report with coverage-plugin NPM package > should run Code cover "message": "Lines 2-3 are not covered in any test case.", "severity": "warning", "source": { - "file": "tmp/e2e/plugin-coverage-e2e/basic-setup/src/index.mjs", + "file": "tmp/e2e/plugin-coverage-e2e/__test__/collect/basic-setup/src/index.mjs", "position": { "endLine": 3, "startLine": 2, @@ -254,7 +254,7 @@ exports[`collect report with coverage-plugin NPM package > should run Code cover "message": "Lines 11-12 are not covered in any test case.", "severity": "warning", "source": { - "file": "tmp/e2e/plugin-coverage-e2e/basic-setup/src/index.mjs", + "file": "tmp/e2e/plugin-coverage-e2e/__test__/collect/basic-setup/src/index.mjs", "position": { "endLine": 12, "startLine": 11, diff --git a/e2e/plugin-coverage-e2e/tests/collect.e2e.test.ts b/e2e/plugin-coverage-e2e/tests/collect.e2e.test.ts index 673c56d63..9dd257adf 100644 --- a/e2e/plugin-coverage-e2e/tests/collect.e2e.test.ts +++ b/e2e/plugin-coverage-e2e/tests/collect.e2e.test.ts @@ -2,18 +2,25 @@ import { cp } from 'node:fs/promises'; import { join } from 'node:path'; import { afterAll, afterEach, beforeAll } from 'vitest'; import { type Report, reportSchema } from '@code-pushup/models'; +import { nxTargetProject } from '@code-pushup/test-nx-utils'; import { teardownTestFolder } from '@code-pushup/test-setup'; -import { omitVariableReportData } from '@code-pushup/test-utils'; +import { + E2E_ENVIRONMENTS_DIR, + TEST_OUTPUT_DIR, + omitVariableReportData, +} from '@code-pushup/test-utils'; import { executeProcess, readJsonFile } from '@code-pushup/utils'; -describe('collect report with coverage-plugin NPM package', () => { - const envRoot = 'tmp/e2e/plugin-coverage-e2e'; - const fixtureDir = join('e2e', 'plugin-coverage-e2e', 'mocks', 'fixtures'); - const basicDir = join(envRoot, 'basic-setup'); - const existingDir = join(envRoot, 'existing-report'); +describe('PLUGIN collect report with coverage-plugin NPM package', () => { + const envRoot = join(E2E_ENVIRONMENTS_DIR, nxTargetProject()); + const testFileDir = join(envRoot, TEST_OUTPUT_DIR, 'collect'); + const basicDir = join(testFileDir, 'basic-setup'); + const existingDir = join(testFileDir, 'existing-report'); + + const fixtureDir = join('e2e', nxTargetProject(), 'mocks', 'fixtures'); beforeAll(async () => { - await cp(fixtureDir, envRoot, { recursive: true }); + await cp(fixtureDir, testFileDir, { recursive: true }); }); afterAll(async () => { await teardownTestFolder(basicDir); diff --git a/e2e/plugin-eslint-e2e/tests/__snapshots__/collect.e2e.test.ts.snap b/e2e/plugin-eslint-e2e/tests/__snapshots__/collect.e2e.test.ts.snap index a7f23fd22..b20625b3b 100644 --- a/e2e/plugin-eslint-e2e/tests/__snapshots__/collect.e2e.test.ts.snap +++ b/e2e/plugin-eslint-e2e/tests/__snapshots__/collect.e2e.test.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`collect report with eslint-plugin NPM package > should run ESLint plugin for flat config and create report.json 1`] = ` +exports[`PLUGIN collect report with eslint-plugin NPM package > should run ESLint plugin for flat config and create report.json 1`] = ` { "packageName": "@code-pushup/core", "plugins": [ @@ -14,7 +14,7 @@ exports[`collect report with eslint-plugin NPM package > should run ESLint plugi "message": "Expected '===' and instead saw '=='.", "severity": "error", "source": { - "file": "tmp/e2e/plugin-eslint-e2e/flat-config/src/index.js", + "file": "tmp/e2e/plugin-eslint-e2e/__test__/flat-config/src/index.js", "position": { "endColumn": 15, "endLine": 6, @@ -58,7 +58,7 @@ Custom options: "message": "'unusedFn' is defined but never used.", "severity": "warning", "source": { - "file": "tmp/e2e/plugin-eslint-e2e/flat-config/src/index.js", + "file": "tmp/e2e/plugin-eslint-e2e/__test__/flat-config/src/index.js", "position": { "endColumn": 18, "endLine": 1, @@ -116,7 +116,7 @@ Custom options: } `; -exports[`collect report with eslint-plugin NPM package > should run ESLint plugin for legacy config and create report.json 1`] = ` +exports[`PLUGIN collect report with eslint-plugin NPM package > should run ESLint plugin for legacy config and create report.json 1`] = ` { "packageName": "@code-pushup/core", "plugins": [ @@ -130,7 +130,7 @@ exports[`collect report with eslint-plugin NPM package > should run ESLint plugi "message": "'unusedFn' is defined but never used.", "severity": "error", "source": { - "file": "tmp/e2e/plugin-eslint-e2e/legacy-config/src/index.js", + "file": "tmp/e2e/plugin-eslint-e2e/__test__/legacy-config/src/index.js", "position": { "endColumn": 18, "endLine": 1, @@ -156,7 +156,7 @@ exports[`collect report with eslint-plugin NPM package > should run ESLint plugi "message": "Unexpected console statement.", "severity": "warning", "source": { - "file": "tmp/e2e/plugin-eslint-e2e/legacy-config/src/index.js", + "file": "tmp/e2e/plugin-eslint-e2e/__test__/legacy-config/src/index.js", "position": { "endColumn": 14, "endLine": 5, diff --git a/e2e/plugin-eslint-e2e/tests/collect.e2e.test.ts b/e2e/plugin-eslint-e2e/tests/collect.e2e.test.ts index ef069bcc1..8160cf91e 100644 --- a/e2e/plugin-eslint-e2e/tests/collect.e2e.test.ts +++ b/e2e/plugin-eslint-e2e/tests/collect.e2e.test.ts @@ -2,16 +2,25 @@ import { cp } from 'node:fs/promises'; import { join } from 'node:path'; import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'; import { type Report, reportSchema } from '@code-pushup/models'; +import { nxTargetProject } from '@code-pushup/test-nx-utils'; import { teardownTestFolder } from '@code-pushup/test-setup'; -import { omitVariableReportData } from '@code-pushup/test-utils'; +import { + E2E_ENVIRONMENTS_DIR, + TEST_OUTPUT_DIR, + omitVariableReportData, +} from '@code-pushup/test-utils'; import { executeProcess, readJsonFile } from '@code-pushup/utils'; -describe('collect report with eslint-plugin NPM package', () => { +describe('PLUGIN collect report with eslint-plugin NPM package', () => { const fixturesDir = join('e2e', 'plugin-eslint-e2e', 'mocks', 'fixtures'); const fixturesFlatConfigDir = join(fixturesDir, 'flat-config'); const fixturesLegacyConfigDir = join(fixturesDir, 'legacy-config'); - const envRoot = join('tmp', 'e2e', 'plugin-eslint-e2e'); + const envRoot = join( + E2E_ENVIRONMENTS_DIR, + nxTargetProject(), + TEST_OUTPUT_DIR, + ); const flatConfigDir = join(envRoot, 'flat-config'); const legacyConfigDir = join(envRoot, 'legacy-config'); const flatConfigOutputDir = join(flatConfigDir, '.code-pushup'); diff --git a/e2e/plugin-lighthouse-e2e/mocks/fixtures/code-pushup.config.lh-default.ts b/e2e/plugin-lighthouse-e2e/mocks/fixtures/default-setup/code-pushup.config.ts similarity index 100% rename from e2e/plugin-lighthouse-e2e/mocks/fixtures/code-pushup.config.lh-default.ts rename to e2e/plugin-lighthouse-e2e/mocks/fixtures/default-setup/code-pushup.config.ts diff --git a/e2e/plugin-lighthouse-e2e/tests/__snapshots__/collect.e2e.test.ts.snap b/e2e/plugin-lighthouse-e2e/tests/__snapshots__/collect.e2e.test.ts.snap index af61a29f4..ccf448795 100644 --- a/e2e/plugin-lighthouse-e2e/tests/__snapshots__/collect.e2e.test.ts.snap +++ b/e2e/plugin-lighthouse-e2e/tests/__snapshots__/collect.e2e.test.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`collect report with lighthouse-plugin NPM package > should run plugin over CLI and creates report.json 1`] = ` +exports[`PLUGIN collect report with lighthouse-plugin NPM package > should run plugin over CLI and creates report.json 1`] = ` { "categories": [ { diff --git a/e2e/plugin-lighthouse-e2e/tests/collect.e2e.test.ts b/e2e/plugin-lighthouse-e2e/tests/collect.e2e.test.ts index 3f174bbc5..22f9ecb73 100644 --- a/e2e/plugin-lighthouse-e2e/tests/collect.e2e.test.ts +++ b/e2e/plugin-lighthouse-e2e/tests/collect.e2e.test.ts @@ -1,45 +1,50 @@ -import { copyFile } from 'node:fs/promises'; +import { cp } from 'node:fs/promises'; import { join } from 'node:path'; -import { afterEach, expect } from 'vitest'; +import { afterAll, beforeAll, expect } from 'vitest'; import { type Report, reportSchema } from '@code-pushup/models'; -import { cleanTestFolder, teardownTestFolder } from '@code-pushup/test-setup'; +import { nxTargetProject } from '@code-pushup/test-nx-utils'; +import { teardownTestFolder } from '@code-pushup/test-setup'; import { + E2E_ENVIRONMENTS_DIR, + TEST_OUTPUT_DIR, omitVariableReportData, removeColorCodes, } from '@code-pushup/test-utils'; import { executeProcess, readJsonFile } from '@code-pushup/utils'; -async function addCodePushupConfig(targetDir: string) { - await cleanTestFolder(targetDir); - await copyFile( - 'e2e/plugin-lighthouse-e2e/mocks/fixtures/code-pushup.config.lh-default.ts', - join(targetDir, 'code-pushup.config.ts'), +describe('PLUGIN collect report with lighthouse-plugin NPM package', () => { + const testFileDir = join( + E2E_ENVIRONMENTS_DIR, + nxTargetProject(), + TEST_OUTPUT_DIR, + 'collect', ); -} + const defaultSetupDir = join(testFileDir, 'default-setup'); -describe('collect report with lighthouse-plugin NPM package', () => { - const baseDir = 'tmp/e2e/plugin-lighthouse-e2e/__test__'; + const fixturesDir = join('e2e', nxTargetProject(), 'mocks/fixtures'); + beforeAll(async () => { + await cp(fixturesDir, testFileDir, { recursive: true }); + }); - afterEach(async () => { - await teardownTestFolder(baseDir); + afterAll(async () => { + await teardownTestFolder(testFileDir); }); it('should run plugin over CLI and creates report.json', async () => { - const cwd = join(baseDir, 'create-report'); - await addCodePushupConfig(cwd); - const { code, stdout } = await executeProcess({ command: 'npx', // verbose exposes audits with perfect scores that are hidden in the default stdout args: ['@code-pushup/cli', 'collect', '--no-progress', '--verbose'], - cwd, + cwd: defaultSetupDir, }); expect(code).toBe(0); const cleanStdout = removeColorCodes(stdout); expect(cleanStdout).toContain('● Largest Contentful Paint'); - const report = await readJsonFile(join(cwd, '.code-pushup', 'report.json')); + const report = await readJsonFile( + join(defaultSetupDir, '.code-pushup', 'report.json'), + ); expect(() => reportSchema.parse(report)).not.toThrow(); expect( omitVariableReportData(report as Report, { omitAuditData: true }), diff --git a/examples/react-todos-app/.eslintrc.js b/examples/react-todos-app/.eslintrc.js deleted file mode 100644 index e05f2bb62..000000000 --- a/examples/react-todos-app/.eslintrc.js +++ /dev/null @@ -1,69 +0,0 @@ -/** @type {import('eslint').ESLint.ConfigData} */ -module.exports = { - root: true, - env: { - browser: true, - es2021: true, - }, - plugins: ['react', 'react-hooks'], - overrides: [ - { - env: { - node: true, - }, - files: ['.eslintrc.{js,cjs}'], - parserOptions: { - sourceType: 'script', - }, - }, - ], - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - ecmaFeatures: { - jsx: true, - }, - }, - settings: { - react: { - version: 'detect', - }, - }, - rules: { - // https://eslint.org/docs/latest/rules/#possible-problems - 'no-cond-assign': 'warn', - 'no-const-assign': 'warn', - 'no-debugger': 'warn', - 'no-invalid-regexp': 'warn', - 'no-undef': 'warn', - 'no-unreachable-loop': 'warn', - 'no-unsafe-negation': 'warn', - 'no-unsafe-optional-chaining': 'warn', - 'no-unused-vars': 'warn', - 'use-isnan': 'warn', - 'valid-typeof': 'warn', - // https://eslint.org/docs/latest/rules/#suggestions - 'arrow-body-style': 'warn', - camelcase: 'warn', - curly: 'warn', - eqeqeq: 'warn', - 'max-lines-per-function': 'warn', - 'max-lines': 'warn', - 'no-shadow': 'warn', - 'no-var': 'warn', - 'object-shorthand': 'warn', - 'prefer-arrow-callback': 'warn', - 'prefer-const': 'warn', - 'prefer-object-spread': 'warn', - yoda: 'warn', - // https://github.com/jsx-eslint/eslint-plugin-react#list-of-supported-rules - 'react/jsx-key': 'warn', - 'react/prop-types': 'warn', - 'react/react-in-jsx-scope': 'warn', - 'react/jsx-uses-vars': 'warn', - 'react/jsx-uses-react': 'error', - // https://www.npmjs.com/package/eslint-plugin-react-hooks - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', - }, -}; diff --git a/examples/react-todos-app/code-pushup.config.js b/examples/react-todos-app/code-pushup.config.js deleted file mode 100644 index 988dca804..000000000 --- a/examples/react-todos-app/code-pushup.config.js +++ /dev/null @@ -1,63 +0,0 @@ -import eslintPlugin from '../../dist/packages/plugin-eslint'; - -const eslintAuditRef = (slug, weight) => ({ - type: 'audit', - plugin: 'eslint', - slug, - weight, -}); - -export default { - persist: { - outputDir: '../../tmp/e2e/react-todos-app', - }, - plugins: [ - await eslintPlugin({ - eslintrc: '.eslintrc.js', - patterns: ['src/**/*.js', 'src/**/*.jsx'], - }), - ], - categories: [ - { - slug: 'bug-prevention', - title: 'Bug prevention', - refs: [ - eslintAuditRef('no-cond-assign', 1), - eslintAuditRef('no-const-assign', 1), - eslintAuditRef('no-debugger', 1), - eslintAuditRef('no-invalid-regexp', 1), - eslintAuditRef('no-undef', 1), - eslintAuditRef('no-unreachable-loop', 1), - eslintAuditRef('no-unsafe-negation', 1), - eslintAuditRef('no-unsafe-optional-chaining', 1), - eslintAuditRef('use-isnan', 1), - eslintAuditRef('valid-typeof', 1), - eslintAuditRef('eqeqeq', 1), - eslintAuditRef('react-jsx-key', 2), - eslintAuditRef('react-prop-types', 1), - eslintAuditRef('react-react-in-jsx-scope', 1), - eslintAuditRef('react-hooks-rules-of-hooks', 2), - eslintAuditRef('react-hooks-exhaustive-deps', 2), - ], - }, - { - slug: 'code-style', - title: 'Code style', - refs: [ - eslintAuditRef('no-unused-vars', 1), - eslintAuditRef('arrow-body-style', 1), - eslintAuditRef('camelcase', 1), - eslintAuditRef('curly', 1), - eslintAuditRef('eqeqeq', 1), - eslintAuditRef('max-lines-per-function', 1), - eslintAuditRef('max-lines', 1), - eslintAuditRef('object-shorthand', 1), - eslintAuditRef('prefer-arrow-callback', 1), - eslintAuditRef('prefer-const', 1), - eslintAuditRef('prefer-object-spread', 1), - eslintAuditRef('yoda', 1), - eslintAuditRef('no-var', 1), - ], - }, - ], -}; diff --git a/examples/react-todos-app/index.html b/examples/react-todos-app/index.html deleted file mode 100644 index 35db2fc31..000000000 --- a/examples/react-todos-app/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - Todos app (React example) - - - - - - -
- - - diff --git a/examples/react-todos-app/package.json b/examples/react-todos-app/package.json deleted file mode 100644 index d9e892a84..000000000 --- a/examples/react-todos-app/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "todo-app", - "private": true, - "scripts": { - "start": "esbuild src/index.jsx --bundle --outdir=www/js --servedir=www", - "build": "esbuild src/index.jsx --bundle --outdir=www/js --minify" - }, - "dependencies": { - "react": "^16.12.0", - "semver": "5.7.1" - }, - "devDependencies": { - "vite": "~4.5.0", - "vitest": "0.34.0" - } -} diff --git a/examples/react-todos-app/project.json b/examples/react-todos-app/project.json deleted file mode 100644 index 3c7bf0c4b..000000000 --- a/examples/react-todos-app/project.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "name": "react-todos-app", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "examples/react-todos-app/src", - "projectType": "application", - "targets": { - "build": { - "executor": "@nx/vite:build", - "outputs": ["{options.outputPath}"], - "defaultConfiguration": "production", - "options": { - "outputPath": "dist/examples/react-todos-app" - }, - "configurations": { - "development": { - "mode": "development" - }, - "production": { - "mode": "production" - } - } - }, - "serve": { - "executor": "@nx/vite:dev-server", - "defaultConfiguration": "development", - "options": { - "buildTarget": "react-todos-app:build" - }, - "configurations": { - "development": { - "buildTarget": "react-todos-app:build:development", - "hmr": true - }, - "production": { - "buildTarget": "react-todos-app:build:production", - "hmr": false - } - } - }, - "test": { - "executor": "@nx/vite:test", - "outputs": ["{options.reportsDirectory}"], - "options": { - "configFile": "examples/react-todos-app/vite.config.ts", - "reportsDirectory": "../../coverage/react-todos-app" - } - }, - "preview": { - "executor": "@nx/vite:preview-server", - "defaultConfiguration": "development", - "options": { - "buildTarget": "react-todos-app:build" - }, - "configurations": { - "development": { - "buildTarget": "react-todos-app:build:development" - }, - "production": { - "buildTarget": "react-todos-app:build:production" - } - } - }, - "run-collect": { - "executor": "nx:run-commands", - "options": { - "command": "npx ../../dist/packages/cli collect --no-progress", - "cwd": "examples/react-todos-app" - }, - "dependsOn": [ - { - "projects": ["cli", "plugin-eslint"], - "target": "build" - } - ] - } - }, - "tags": ["scope:internal", "type:app"] -} diff --git a/examples/react-todos-app/public/favicon.ico b/examples/react-todos-app/public/favicon.ico deleted file mode 100644 index 317ebcb23..000000000 Binary files a/examples/react-todos-app/public/favicon.ico and /dev/null differ diff --git a/examples/react-todos-app/src/App.jsx b/examples/react-todos-app/src/App.jsx deleted file mode 100644 index 1b8dcbea1..000000000 --- a/examples/react-todos-app/src/App.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import CreateTodo from './components/CreateTodo'; -import TodoFilter from './components/TodoFilter'; -import TodoList from './components/TodoList'; -import { useTodos } from './hooks/useTodos'; - -const App = () => { - const { loading, todos, onCreate, onEdit, setQuery, setHideComplete } = - useTodos(); - - return ( -
-

TODOs

- - - -
- ); -}; - -export default App; diff --git a/examples/react-todos-app/src/App.test.jsx b/examples/react-todos-app/src/App.test.jsx deleted file mode 100644 index c8e5667bc..000000000 --- a/examples/react-todos-app/src/App.test.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import '@testing-library/jest-dom'; -import { render, screen } from '@testing-library/react'; -import React from 'react'; -import { describe, expect, it } from 'vitest'; -import App from './App'; - -describe('App', () => { - it('should display the app title', async () => { - render(); - - expect(screen.getByRole('heading')).toHaveTextContent('TODOs'); - }); - - it('should display an Add button', async () => { - render(); - - expect(screen.getByRole('button')).toBeVisible(); - expect(screen.getByRole('button')).toHaveTextContent('Add'); - }); -}); diff --git a/examples/react-todos-app/src/components/CreateTodo.jsx b/examples/react-todos-app/src/components/CreateTodo.jsx deleted file mode 100644 index 63cdf9f47..000000000 --- a/examples/react-todos-app/src/components/CreateTodo.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import React, { useState } from 'react'; - -const CreateTodo = props => { - const [title, setTitle] = useState(''); - - return ( -
{ - event.preventDefault(); - props.onCreate(title); - setTitle(''); - }} - > - { - setTitle(event.target.value); - }} - /> - -
- ); -}; - -export default CreateTodo; diff --git a/examples/react-todos-app/src/components/TodoFilter.jsx b/examples/react-todos-app/src/components/TodoFilter.jsx deleted file mode 100644 index 01985dbed..000000000 --- a/examples/react-todos-app/src/components/TodoFilter.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; - -const TodoFilter = props => { - return ( -
- { - props.setQuery(event.target.value); - }} - /> - - -
- ); -}; - -export default TodoFilter; diff --git a/examples/react-todos-app/src/components/TodoList.jsx b/examples/react-todos-app/src/components/TodoList.jsx deleted file mode 100644 index 3a3dbc8f7..000000000 --- a/examples/react-todos-app/src/components/TodoList.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import moment from 'moment'; -import React from 'react'; - -const TodoList = props => ( -
    - {props.todos.map(todo => ( -
  • - -
  • - ))} -
-); - -export default TodoList; diff --git a/examples/react-todos-app/src/hooks/useTodos.js b/examples/react-todos-app/src/hooks/useTodos.js deleted file mode 100644 index 01091cfc0..000000000 --- a/examples/react-todos-app/src/hooks/useTodos.js +++ /dev/null @@ -1,73 +0,0 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; - -export const useTodos = () => { - const [loading, setLoading] = useState(false); - const [data, setData] = useState([]); - - useEffect(() => { - setLoading(true); - fetch('https://jsonplaceholder.typicode.com/todos') - .then(resp => resp.json()) - .then(data => { - setData(data); - setLoading(false); - }); - }, []); - - const onCreate = useCallback(title => { - const body = JSON.stringify({ - title: title, - complete: false, - }); - - fetch('https://jsonplaceholder.typicode.com/todos', { - method: 'POST', - body, - }) - .then(resp => resp.json()) - .then(({ id }) => { - setData(data => [ - ...data, - { - id: id, - title: title, - complete: false, - }, - ]); - }); - }); - - const onEdit = useCallback(todo => { - setData(data => data.map(t => (t.id == todo.id ? todo : t))); - fetch(`https://jsonplaceholder.typicode.com/todos/${todo.id}`, { - method: 'PUT', - body: JSON.stringify(todo), - }); - }); - - const [query, setQuery] = useState(''); - const [hideComplete, setHideComplete] = useState(false); - - const todos = useMemo( - () => - data.filter(todo => { - if (query && !todo.title.toLowerCase().includes(query.toLowerCase())) { - return false; - } - if (hideComplete && todo.complete) { - return false; - } - return true; - }), - [data, query, hideComplete], - ); - - return { - loading, - todos, - onCreate, - onEdit, - setQuery, - setHideComplete, - }; -}; diff --git a/examples/react-todos-app/src/index.jsx b/examples/react-todos-app/src/index.jsx deleted file mode 100644 index 6ee32ccaf..000000000 --- a/examples/react-todos-app/src/index.jsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import { createRoot } from 'react-dom/client'; -import App from './App'; - -let root = createRoot(document.querySelector('#root')); -root.render(); diff --git a/examples/react-todos-app/test-setup.js b/examples/react-todos-app/test-setup.js deleted file mode 100644 index 141cd45f9..000000000 --- a/examples/react-todos-app/test-setup.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as matchers from '@testing-library/jest-dom/matchers'; -import { cleanup } from '@testing-library/react'; -import { afterEach, expect } from 'vitest'; - -expect.extend(matchers); - -afterEach(() => { - cleanup(); -}); diff --git a/examples/react-todos-app/tsconfig.app.json b/examples/react-todos-app/tsconfig.app.json deleted file mode 100644 index a86621c8c..000000000 --- a/examples/react-todos-app/tsconfig.app.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../../dist/out-tsc", - "types": [ - "node", - "@nx/react/typings/cssmodule.d.ts", - "@nx/react/typings/image.d.ts", - "vite/client" - ] - }, - "exclude": [ - "vite.config.ts", - "src/**/*.spec.ts", - "src/**/*.test.ts", - "src/**/*.spec.tsx", - "src/**/*.test.tsx", - "src/**/*.spec.js", - "src/**/*.test.js", - "src/**/*.spec.jsx", - "src/**/*.test.jsx" - ], - "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] -} diff --git a/examples/react-todos-app/tsconfig.json b/examples/react-todos-app/tsconfig.json deleted file mode 100644 index fdfab6c30..000000000 --- a/examples/react-todos-app/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "jsx": "react-jsx", - "allowJs": false, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "types": ["vite/client", "vitest"] - }, - "files": [], - "include": [], - "references": [ - { - "path": "./tsconfig.app.json" - }, - { - "path": "./tsconfig.spec.json" - } - ], - "extends": "../../tsconfig.base.json" -} diff --git a/examples/react-todos-app/tsconfig.spec.json b/examples/react-todos-app/tsconfig.spec.json deleted file mode 100644 index 9a202f348..000000000 --- a/examples/react-todos-app/tsconfig.spec.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../../dist/out-tsc", - "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node"] - }, - "include": [ - "vite.config.ts", - "src/**/*.test.js", - "src/**/*.spec.js", - "src/**/*.test.jsx", - "src/**/*.spec.jsx", - "src/**/*.d.ts" - ], - "types": ["@nx/react/typings/cssmodule.d.ts", "@nx/react/typings/image.d.ts"] -} diff --git a/examples/react-todos-app/vite.config.ts b/examples/react-todos-app/vite.config.ts deleted file mode 100644 index 91e6f3d1b..000000000 --- a/examples/react-todos-app/vite.config.ts +++ /dev/null @@ -1,44 +0,0 @@ -/// -import react from '@vitejs/plugin-react'; -import { dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { defineConfig } from 'vite'; - -export default defineConfig({ - root: fileURLToPath(dirname(import.meta.url)), - build: { - outDir: '../../dist/examples/react-todos-app', - emptyOutDir: true, - reportCompressedSize: true, - }, - cacheDir: '../../node_modules/.vite/react-todos-app', - - server: { - port: 3000, - host: 'localhost', - }, - - preview: { - port: 3100, - host: 'localhost', - }, - - plugins: [react()], - - test: { - reporters: ['basic'], - globals: true, - cache: { - dir: '../../node_modules/.vitest', - }, - coverage: { - reporter: ['lcov', 'text'], - provider: 'v8', - reportsDirectory: '../../coverage/react-todos-app', - include: ['src/**/*.{js,jsx}'], - }, - environment: 'jsdom', - include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], - setupFiles: ['test-setup.js'], - }, -}); diff --git a/global-setup.e2e.ts b/global-setup.e2e.ts deleted file mode 100644 index 2ae31fc30..000000000 --- a/global-setup.e2e.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { rm, writeFile } from 'node:fs/promises'; -import { join } from 'node:path'; -import { setup as globalSetup } from './global-setup'; -import { setupTestFolder, teardownTestFolder } from './testing/test-setup/src'; -import { - nxRunManyNpmInstall, - nxRunManyNpmUninstall, -} from './tools/src/npm/utils'; -import { findLatestVersion, nxRunManyPublish } from './tools/src/publish/utils'; -import { START_VERDACCIO_SERVER_TARGET_NAME } from './tools/src/verdaccio/constants'; -import startLocalRegistry, { - RegistryResult, -} from './tools/src/verdaccio/start-local-registry'; -import stopLocalRegistry from './tools/src/verdaccio/stop-local-registry'; -import { uniquePort } from './tools/src/verdaccio/utils'; - -const e2eDir = join('tmp', 'e2e', 'react-todos-app'); -const uniqueDir = join(e2eDir, `registry-${uniquePort()}`); - -let activeRegistry: RegistryResult; - -export async function setup() { - await globalSetup(); - await setupTestFolder(e2eDir); - - try { - activeRegistry = await startLocalRegistry({ - localRegistryTarget: `@code-pushup/cli-source:${START_VERDACCIO_SERVER_TARGET_NAME}`, - storage: join(uniqueDir, 'storage'), - port: uniquePort(), - }); - } catch (error) { - console.error('Error starting local verdaccio registry:\n' + error.message); - throw error; - } - - // package publish - const { registry } = activeRegistry.registryData; - await writeFile('.npmrc', `@code-pushup:registry=${registry}`); - try { - console.info('Publish packages'); - nxRunManyPublish({ - registry, - nextVersion: findLatestVersion(), - parallel: 1, - }); - } catch (error) { - console.error('Error publishing packages:\n' + error.message); - throw error; - } - - // package install - try { - console.info('Installing packages'); - nxRunManyNpmInstall({ registry, parallel: 1 }); - } catch (error) { - console.error('Error installing packages:\n' + error.message); - throw error; - } -} - -export async function teardown() { - if (activeRegistry && 'registryData' in activeRegistry) { - const { stop } = activeRegistry; - - stopLocalRegistry(stop); - nxRunManyNpmUninstall({ parallel: 1 }); - } - await rm('.npmrc'); - await teardownTestFolder(e2eDir); -} diff --git a/global-setup.verdaccio.ts b/global-setup.verdaccio.ts deleted file mode 100644 index e1aa9f958..000000000 --- a/global-setup.verdaccio.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { setup as globalSetup } from './global-setup'; -import { executeProcess, objectToCliArgs } from './packages/utils/src'; -import { - VerdaccioEnvResult, - nxStartVerdaccioAndSetupEnv, - nxStopVerdaccioAndTeardownEnv, -} from './tools/src/verdaccio/env'; - -let activeRegistry: VerdaccioEnvResult; -const projectName = process.env['NX_TASK_TARGET_PROJECT']; - -export async function setup() { - await globalSetup(); - - try { - activeRegistry = await nxStartVerdaccioAndSetupEnv({ - projectName: projectName, - verbose: true, - }); - } catch (error) { - console.error('Error starting local verdaccio registry:\n' + error.message); - throw error; - } - - const { userconfig, workspaceRoot } = activeRegistry; - await executeProcess({ - command: 'npx', - args: objectToCliArgs({ - _: ['nx', 'setup-deps', projectName], - registry: activeRegistry.registry.url, // publish - userconfig, // publish & install - prefix: workspaceRoot, // install - }), - observer: { onStdout: stdout => console.info(stdout) }, - }); -} - -export async function teardown() { - // NOTICE - Time saving optimization - // We skip uninstalling packages as the folder is deleted anyway - - await nxStopVerdaccioAndTeardownEnv(activeRegistry); -} diff --git a/nx.json b/nx.json index e347930b3..23a1511fb 100644 --- a/nx.json +++ b/nx.json @@ -106,25 +106,6 @@ "filterByTags": ["publishable"] } } - }, - "./tools/src/debug/debug.plugin.ts", - { - "plugin": "./tools/src/npm/npm.plugin.ts", - "options": { - "verbose": true - } - }, - { - "plugin": "./tools/src/publish/publish.plugin.ts", - "options": { - "verbose": true - } - }, - { - "plugin": "./tools/src/verdaccio/verdaccio.plugin.ts", - "options": { - "verbose": true - } } ] } diff --git a/testing/test-nx-utils/project.json b/testing/test-nx-utils/project.json index 5e60491be..72d84d1c7 100644 --- a/testing/test-nx-utils/project.json +++ b/testing/test-nx-utils/project.json @@ -29,5 +29,5 @@ } } }, - "tags": ["scope:tooling", "type:testing"] + "tags": ["scope:shared", "type:testing"] } diff --git a/testing/test-nx-utils/src/index.ts b/testing/test-nx-utils/src/index.ts index 972c232dc..74a844499 100644 --- a/testing/test-nx-utils/src/index.ts +++ b/testing/test-nx-utils/src/index.ts @@ -1,3 +1,4 @@ +export * from './lib/utils/environment'; export * from './lib/utils/nx'; export * from './lib/utils/nx-plugin'; export * from './lib/utils/tree'; diff --git a/testing/test-nx-utils/src/lib/utils/environment.ts b/testing/test-nx-utils/src/lib/utils/environment.ts new file mode 100644 index 000000000..09c2e65c1 --- /dev/null +++ b/testing/test-nx-utils/src/lib/utils/environment.ts @@ -0,0 +1,9 @@ +export const nxTargetProject = () => { + const project = process.env['NX_TASK_TARGET_PROJECT']; + if (project == null) { + throw new Error( + 'Process environment variable NX_TASK_TARGET_PROJECT is undefined.', + ); + } + return project; +}; diff --git a/testing/test-utils/src/lib/constants.ts b/testing/test-utils/src/lib/constants.ts index e6c076dac..dd76c5663 100644 --- a/testing/test-utils/src/lib/constants.ts +++ b/testing/test-utils/src/lib/constants.ts @@ -1,3 +1,6 @@ +export const E2E_ENVIRONMENTS_DIR = 'tmp/e2e'; +export const TEST_OUTPUT_DIR = '__test__'; +export const TEST_SNAPSHOTS_DIR = '__snapshots__'; export const MEMFS_VOLUME = '/test'; export const ISO_STRING_REGEXP = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; diff --git a/tools/src/npm/README.md b/tools/src/npm/README.md deleted file mode 100644 index c8cbd6adb..000000000 --- a/tools/src/npm/README.md +++ /dev/null @@ -1,147 +0,0 @@ -# TODO - -Reduce file interactions: -https://docs.npmjs.com/cli/v8/commands/npm-install#package-lock -`--no-package-lock` - -Reduce target install folder -https://docs.npmjs.com/cli/v8/commands/npm-install#global (explains the prefix flag) -`--prefix=${join('tmp',packageName,'node_modules')}` - -# NPM Nx Plugin - -A Nx plugin that adds targets that help to work with packages published to NPM. - -## Usage - -Register the plugin in your `nx.json` - -```jsonc -// nx.json -{ - //... - "plugins": ["tools/npm/npm.plugin.ts"], -} -``` - -### Options - -You can configure the plugin by providing options object in addition to the plugin path - -**Options:** - -| Name | Type | Default | Description | -| -------------------- | ---------------------- | --------------------------- | ----------------------------------------------- | -| `verbose` | `boolean` | `false` | Log additional information. | -| `tsconfig` | `string` | `tools/tsconfig.tools.json` | The tsconfig file to use. | -| `npmCheckScript` | `string` | `check-package-range.ts` | The script to execute when checking a package. | -| `publishableTargets` | `string` or `string[]` | `["publishable"]` | The targets that mark a project as publishable. | - -Example: - -```jsonc -// nx.json -{ - //... - "plugins": [ - { - "plugin": "tools/npm/npm.plugin.ts", - "options": { - "tsconfig": "tools/tsconfig.tools.json", - "npmCheckScript": "check-package-range.ts", - "publishableTargets": ["add-to-npm-registry"], - }, - }, - ], -} -``` - -### Nx tags - -> [!NOTE] -> A project can be marked as publishable using `tags` in `project.json`. -> Default tag name is `publishable`. - -#### `npm-check` - -Added dynamically to every project that is publishable (has a target named `publishable`). -Checks if a given package is registered in a given registry. - -It will automatically use `tools/tsconfig.tools.ts` to execute the script as well as derives the package name from the project name from `package/root/package.json`. -By default, it registers the latest version of the package from the default registry. - -Run: -`nx run :npm-check` - -**Options:** - -| Name | Type | Default | Description | -| ------------ | -------- | ---------------------------- | -------------------------------------------------- | -| `pkgVersion` | `string` | `latest` | The package version to check. | -| `registry` | `string` | `https://registry.npmjs.org` | The registry to check the package version against. | - -Examples: - -- `nx run :npm-check` -- `nx run :npm-check --registry=http://localhost:58999` -- `nx run :npm-check --registry=http://localhost:58999 --pkgVersion=1.0.0` - -#### `npm-install` - -Added dynamically to every project that is publishable (has a target named `publishable`). -Installs a given package version from a given registry. - -It will automatically derive the package name from the project name from `package/root/package.json`. -By default, it installs the latest version of the package from the default registry. - -Run: -`nx run :npm-install` - -**Options:** - -| Name | Type | Default | Description | -| ------------ | -------- | ---------------------------- | ----------------------------------------- | -| `pkgVersion` | `string` | `latest` | The package version to install. | -| `registry` | `string` | `https://registry.npmjs.org` | The registry to install the package from. | - -Examples: - -- `nx run :npm-install` -- `nx run :npm-install --pkgVersion=1.0.0` -- `nx run :npm-install --pkgVersion=1.0.0 --registry=http://localhost:58999` - -#### `npm-uninstall` - -Added dynamically to every project that is publishable (has a target named `publishable`). -Uninstalls a given package. - -By default, it uninstalls the latest version of the package from the package.json in your CWD. - -Run: -`nx run :npm-uninstall` - -Examples: - -- `nx run :npm-uninstall` - -## Scripts - -### `check-package-range.ts` - -Checks if a given package is registered in a given registry. - -Run: -`tsx --tsconfig=tools/tsconfig.tools.json tools/npm/check-package-range.ts` - -**Options:** - -| Name | Type | Default | Description | -| ------------ | -------- | ---------------------------- | -------------------------------------------------- | -| `pkgVersion` | `string` | `latest` | The package version to check. | -| `registry` | `string` | `https://registry.npmjs.org` | The registry to check the package version against. | - -Examples: - -- `tsx --tsconfig=tools/tsconfig.tools.json tools/npm/check-package-range.ts` -- `tsx --tsconfig=tools/tsconfig.tools.json tools/npm/check-package-range.ts --registry=http://localhost:58999` -- `tsx --tsconfig=tools/tsconfig.tools.json tools/npm/check-package-range.ts --registry=http://localhost:58999 --pkgVersion=1.0.0` diff --git a/tools/src/npm/bin/check-package-range.ts b/tools/src/npm/bin/check-package-range.ts deleted file mode 100644 index 89c586b18..000000000 --- a/tools/src/npm/bin/check-package-range.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { execFileSync, execSync } from 'node:child_process'; -import yargs from 'yargs'; -import { hideBin } from 'yargs/helpers'; -import { objectToCliArgs } from '../../../../packages/utils/src'; -import type { NpmCheckOptions, NpmCheckResult } from '../types'; - -const argv = yargs(hideBin(process.argv)) - .options({ - pkgRange: { type: 'string', demandOption: true }, - registry: { type: 'string' }, - }) - .coerce('pkgRange', rawVersion => { - if (rawVersion != null && rawVersion !== '') { - return rawVersion; - } else { - return undefined; - } - }) - .coerce('registry', rawRegistry => { - if (rawRegistry != null && rawRegistry !== '') { - return rawRegistry; - } else { - return undefined; - } - }).argv; - -const { pkgRange, registry = 'https://registry.npmjs.org/' } = - argv as NpmCheckOptions; - -try { - const command = 'npm'; - const args = objectToCliArgs({ - _: ['view', pkgRange], - registry, - }); - - const viewResult = execFileSync( - command, - [ - ...args, - // Hide process output via "2>/dev/null". Otherwise, it will print the error message to the terminal. - '2>/dev/null', - ], - { - shell: true, - }, - ).toString(); - - const existingPackage = viewResult - .split('\n') - .filter(Boolean) - .at(0) - ?.split(' ') - .at(0); - console.log(`${existingPackage}#FOUND` satisfies NpmCheckResult); // process output to parse - process.exit(0); -} catch (error) { - // @TODO we use '2>/dev/null' to hide errors from process output, but also can't check error message. Find better solution. - // if (error.message.includes(`npm ERR! 404 '${pkgRange}' is not in this registry`)) { - console.log(`${pkgRange}#NOT_FOUND` satisfies NpmCheckResult); // process output to parse - process.exit(0); - // } -} diff --git a/tools/src/npm/constants.ts b/tools/src/npm/constants.ts deleted file mode 100644 index f7ae15e81..000000000 --- a/tools/src/npm/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const NPM_CHECK_SCRIPT = 'tools/src/npm/bin/check-package-range.ts'; diff --git a/tools/src/npm/npm.plugin.ts b/tools/src/npm/npm.plugin.ts deleted file mode 100644 index f0dd78633..000000000 --- a/tools/src/npm/npm.plugin.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { - type CreateNodes, - type CreateNodesContext, - readJsonFile, -} from '@nx/devkit'; -import { dirname, join } from 'node:path'; -import type { ProjectConfiguration } from 'nx/src/config/workspace-json-project-json'; -import { NPM_CHECK_SCRIPT } from './constants'; - -type CreateNodesOptions = { - tsconfig?: string; - npmCheckScript?: string; - verbose?: boolean; - publishableTags?: string; -}; - -export const createNodes: CreateNodes = [ - '**/project.json', - ( - projectConfigurationFile: string, - opts: undefined | unknown, - context: CreateNodesContext, - ) => { - const root = dirname(projectConfigurationFile); - const projectConfiguration: ProjectConfiguration = readJsonFile( - join(process.cwd(), projectConfigurationFile), - ); - const { - publishableTags = 'publishable', - tsconfig = 'tools/tsconfig.tools.json', - npmCheckScript = NPM_CHECK_SCRIPT, - verbose = false, - } = (opts ?? {}) as CreateNodesOptions; - - const isPublishable = (projectConfiguration?.tags ?? []).some(target => - publishableTags.includes(target), - ); - if (!isPublishable) { - return {}; - } - - return { - projects: { - [root]: { - targets: npmTargets({ root, tsconfig, npmCheckScript, verbose }), - }, - }, - }; - }, -]; - -function npmTargets({ - root, - tsconfig, - npmCheckScript, - verbose, -}: Required> & { - root: string; -}) { - const { name: packageName } = readJsonFile(join(root, 'package.json')); - return { - 'npm-check': { - command: `tsx --tsconfig={args.tsconfig} {args.script} --pkgRange=${packageName}@{args.pkgVersion} --registry={args.registry} --verbose=${verbose}`, - options: { - script: npmCheckScript, - tsconfig, - }, - }, - 'npm-install': { - dependsOn: [ - { project: 'dependencies', targets: 'npm-install', params: 'forward' }, - ], - command: `npm install -D ${packageName} --prefix={args.prefix} --userconfig={args.userconfig}`, - }, - 'npm-uninstall': { - command: `npm uninstall ${packageName} --prefix={args.prefix} --userconfig={args.userconfig}`, - }, - 'npm-install-e2e': { - dependsOn: [ - { - target: 'publish-e2e', - projects: 'self', - params: 'forward', - }, - { - target: 'npm-install-e2e', - projects: 'dependencies', - params: 'forward', - }, - { - target: 'publish-e2e', - projects: 'dependencies', - params: 'forward', - }, - ], - command: `npm install -D --no-fund ${packageName}@{args.pkgVersion} --prefix={args.prefix} --userconfig={args.userconfig}`, - }, - }; -} diff --git a/tools/src/npm/types.ts b/tools/src/npm/types.ts deleted file mode 100644 index 051162f18..000000000 --- a/tools/src/npm/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type NpmCheckToken = 'FOUND' | `NOT_FOUND`; -export type NpmCheckResult = `${string}#${NpmCheckToken}`; -export type NpmCheckOptions = { - pkgRange: string; - registry: string; -}; diff --git a/tools/src/npm/utils.ts b/tools/src/npm/utils.ts deleted file mode 100644 index 593a655ec..000000000 --- a/tools/src/npm/utils.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { execFileSync } from 'node:child_process'; -import { objectToCliArgs } from '../../../packages/utils/src'; - -export type NpmInstallOptions = { - directory?: string; - prefix?: string; - registry?: string; - userconfig?: string; - tag?: string; - pkgVersion?: string; - parallel?: number; -}; - -export function nxRunManyNpmInstall({ - registry, - prefix, - userconfig, - tag = 'e2e', - pkgVersion, - directory, - parallel, -}: NpmInstallOptions) { - console.info( - `Installing packages in ${directory} from registry: ${registry}.`, - ); - try { - execFileSync( - 'nx', - [ - ...objectToCliArgs({ - _: ['run-many'], - targets: 'npm-install', - ...(parallel ? { parallel } : {}), - ...(pkgVersion ? { pkgVersion } : {}), - ...(tag ? { tag } : {}), - ...(registry ? { registry } : {}), - ...(userconfig ? { userconfig } : {}), - ...(prefix ? { prefix } : {}), - }), - ], - { - env: process.env, - stdio: 'inherit', - shell: true, - cwd: directory ?? process.cwd(), - }, - ); - } catch (error) { - console.error('Error installing packages:\n' + error.message); - throw error; - } -} - -export function nxRunManyNpmUninstall({ - parallel, - ...opt -}: { - prefix?: string; - userconfig?: string; - parallel?: number; -}) { - console.info('Uninstalling all NPM packages.'); - try { - execFileSync( - 'npx', - objectToCliArgs({ - _: ['nx', 'run-many'], - targets: 'npm-uninstall', - parallel, - ...opt, - }), - { env: process.env, stdio: 'inherit', shell: true }, - ); - } catch (error) { - console.error('Uninstalling all NPM packages failed.'); - } -} diff --git a/tools/src/publish/README.md b/tools/src/publish/README.md deleted file mode 100644 index 38f9c4ded..000000000 --- a/tools/src/publish/README.md +++ /dev/null @@ -1,139 +0,0 @@ -# TODO - -- refactor version bumping -- reconsider latest version detection logic -- use executeProcess instead of execSync -- refactor targets to use objToCliArgs - -# Publish Nx Plugin - -A Nx plugin that adds targets that help to publish packages to NPM. - -## Usage - -Register the plugin in your `nx.json` - -```jsonc -// nx.json -{ - "name": "my-project", - "plugins": ["tools/publish/publish.plugin.ts"], -} -``` - -### Options - -You can configure the plugin by providing options object in addition to the plugin path - -**Options:** - -| Name | Type | Default | Description | -| -------------------- | ---------------------- | --------------------------- | ----------------------------------------------- | -| `verbose` | `boolean` | `false` | Log additional information. | -| `tsconfig` | `string` | `tools/tsconfig.tools.json` | The tsconfig file to use. | -| `publishScript` | `string` | `publish-package.ts` | The script to execute when publishing. | -| `publishableTargets` | `string` or `string[]` | `["publishable"]` | The targets that mark a project as publishable. | - -Example: - -```jsonc -// nx.json -{ - "name": "my-project", - "plugins": [ - { - "plugin": "tools/publish/publish.plugin.ts", - "options": { - "tsconfig": "tools/tsconfig.tools.json", - "publishScript": "publish-package.ts", - "publishableTargets": ["add-to-npm-registry"], - }, - }, - ], -} -``` - -### Targets - -> [!NOTE] -> A project can be marked as publishable by adding an empty target named `publishable`. - -#### `publish` - -Added dynamically to every project that is publishable (has a target named `publishable`). -Publishes a package to a given registry. - -It will automatically use `tools/tsconfig.tools.ts` to execute the script as well as derives the package name from the project name from `package/root/package.json`. -By default, it registers the latest version of the package from the default registry. - -Run: -`nx run :publish` - -**Options:** - -| Name | Type | Default | Description | -| ------------ | -------- | ---------------------------- | --------------------------------------- | -| `pkgVersion` | `string` | `latest` | The package version to publish. | -| `registry` | `string` | `https://registry.npmjs.org` | The registry to publish the package to. | - -Examples: - -- `nx run :publish` -- `nx run :publish --registry=http://localhost:58999` -- `nx run :publish --registry=http://localhost:58999 --pkgVersion=1.0.0` - -## Scripts - -### `publish-package.ts` - -The script that is executed when running the `publish` target. - -Options: - -| Name | Type | Default | Description | -| ------------- | --------- | -------- | -------------------------------------------------------------------- | -| `nextVersion` | `string` | `latest` | The version to publish. | -| `tag` | `string` | `latest` | The tag to publish. | -| `registry` | `string` | | The registry to publish the package to. | -| `directory` | `string` | | The directory to publish. (location of the build artefact e.g. dist) | -| `verbose` | `boolean` | `false` | Log additional information. | - -Examples: - -- `tsx tools/src/publish/bin/publish-package.ts` -- `tsx tools/src/publish/bin/publish-package.ts --directory=dist/packages/` -- `tsx tools/src/publish/bin/publish-package.ts --directory=dist/packages/ --registry=http://localhost:58999 --nextVersion=1.0.0` - -### `bump-package.ts` - -This script is used to bump the version of the package. It will automatically update the version in the `package.json` file. - -Options: - -| Name | Type | Default | Description | -| ------------- | --------- | --------------- | -------------------------------------------------------------------- | -| `directory` | `string` | `process.cwd()` | The directory to publish. (location of the build artefact e.g. dist) | -| `nextVersion` | `string` | | The version to publish. | -| `verbose` | `boolean` | `false` | Log additional information. | - -Examples: - -- `tsx tools/src/publish/bin/bump-package.ts --nextVersion=` -- `tsx tools/src/publish/bin/bump-package.ts --directory=dist/packages/ --nextVersion=` - -### `login-check.ts` - -This script is used to check if the user is logged in to the NPM registry. - -Options: - -| Name | Type | Default | Description | -| ---------- | --------- | ---------------------------- | --------------------------- | -| `registry` | `string` | `https://registry.npmjs.org` | The registry to check. | -| `verbose` | `boolean` | `false` | Log additional information. | - -Examples: - -- `tsx tools/src/publish/bin/login-check.ts` -- `tsx tools/src/publish/bin/login-check.ts --registry=http://localhost:58999` -- `tsx tools/src/publish/bin/login-check.ts --registry=http://localhost:58999 --verbose` diff --git a/tools/src/publish/bin/bump-package.ts b/tools/src/publish/bin/bump-package.ts deleted file mode 100644 index efd610e06..000000000 --- a/tools/src/publish/bin/bump-package.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { readFileSync, writeFileSync } from 'node:fs'; -import { join } from 'node:path'; -import yargs from 'yargs'; -import { hideBin } from 'yargs/helpers'; -import { parseVersion } from '../../utils'; -import type { BumpOptions } from '../types'; - -const argv = yargs(hideBin(process.argv)) - .options({ - nextVersion: { - type: 'string', - }, - verbose: { type: 'boolean' }, - }) - .coerce('nextVersion', parseVersion).argv; - -const { - nextVersion: version, - verbose, - directory = process.cwd(), -} = argv as BumpOptions; -// Updating the version in "package.json" -const packageJsonFile = join(directory, 'package.json'); -try { - const packageJson = JSON.parse(readFileSync(packageJsonFile).toString()); - if (version != null) { - if (packageJson.version === version) { - console.info(`Package version is already set to ${version}.`); - } - - console.info( - `Updating ${packageJsonFile} version from ${packageJson.version} to ${version}`, - ); - writeFileSync( - packageJsonFile, - JSON.stringify( - { ...packageJson, version, description: 'E2E test' }, - null, - 2, - ), - ); - process.exit(0); - } - // @TODO: Implement autodetect version bump - console.info( - 'Autodetecting version bump not yet implemented, exiting with error', - ); - process.exit(1); -} catch (e) { - console.info(`Error updating version in ${packageJsonFile} file.`); - process.exit(1); -} diff --git a/tools/src/publish/bin/publish-package.ts b/tools/src/publish/bin/publish-package.ts deleted file mode 100644 index eca0e7d57..000000000 --- a/tools/src/publish/bin/publish-package.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * This is a minimal script to publish your package to "npm". - * This is meant to be used as-is or customize as you see fit. - * - * This script is executed on "dist/path/to/library" as "cwd" by default. - * - * You might need to authenticate with NPM before running this script. - */ -import { execSync } from 'node:child_process'; -import { readFileSync } from 'node:fs'; -import { join, relative } from 'node:path'; -import { DEFAULT_REGISTRY } from 'verdaccio/build/lib/constants'; -import yargs from 'yargs'; -import { hideBin } from 'yargs/helpers'; -import { objectToCliArgs } from '../../../../packages/utils/src'; -import { parseVersion } from '../../utils'; -import type { PublishOptions } from '../types'; -import { findLatestVersion, nxBumpVersion } from '../utils'; - -const argv = yargs(hideBin(process.argv)) - .options({ - directory: { type: 'string' }, - projectName: { type: 'string' }, - nextVersion: { type: 'string' }, - tag: { type: 'string', default: 'next' }, - registry: { type: 'string' }, - userconfig: { type: 'string' }, - verbose: { type: 'boolean' }, - }) - .coerce('nextVersion', parseVersion).argv; -const { - directory = process.cwd(), - projectName, - nextVersion, - tag, - registry = DEFAULT_REGISTRY, - userconfig, - verbose, -} = argv as PublishOptions; -const version = nextVersion ?? findLatestVersion(); - -// Updating the version in "package.json" before publishing -nxBumpVersion({ nextVersion: version, directory, projectName }); - -try { - execSync( - objectToCliArgs({ - _: ['npm', 'publish'], - access: 'public', - ...(tag ? { tag } : {}), - ...(registry ? { registry } : {}), - ...(userconfig - ? { - userconfig: relative( - join(process.cwd(), directory ?? ''), - join(process.cwd(), userconfig), - ), - } - : {}), - }).join(' '), - { - cwd: directory, - }, - ); -} catch (error) { - if ( - (error as Error).message.includes( - `need auth This command requires you to be logged in to ${registry}`, - ) - ) { - console.info( - `Authentication error! Check if your registry is set up correctly. If you publish to a public registry run login before.`, - ); - process.exit(1); - } else if ( - error instanceof Error && - error.message.includes(`Cannot publish over existing version`) - ) { - console.info(`Version ${version} already published to ${registry}.`); - process.exit(0); - } - throw error; -} -process.exit(0); diff --git a/tools/src/publish/constants.ts b/tools/src/publish/constants.ts deleted file mode 100644 index ed15a9451..000000000 --- a/tools/src/publish/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const PUBLISH_SCRIPT = 'tools/src/publish/bin/publish-package.ts'; -export const BUMP_SCRIPT = 'tools/src/publish/bin/bump-package.ts'; -export const LOGIN_CHECK_SCRIPT = 'tools/src/publish/bin/login-check.ts'; diff --git a/tools/src/publish/publish.plugin.ts b/tools/src/publish/publish.plugin.ts deleted file mode 100644 index 3a20450f9..000000000 --- a/tools/src/publish/publish.plugin.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { - type CreateNodes, - type CreateNodesContext, - readJsonFile, -} from '@nx/devkit'; -import { dirname } from 'node:path'; -import type { ProjectConfiguration } from 'nx/src/config/workspace-json-project-json'; -import { BUMP_SCRIPT, PUBLISH_SCRIPT } from './constants'; - -type CreateNodesOptions = { - tsconfig?: string; - publishableTags?: string | string[]; - publishScript?: string; - bumpScript?: string; - directory?: string; - verbose?: boolean; -}; -export const createNodes: CreateNodes = [ - '**/project.json', - ( - projectConfigurationFile: string, - opts: undefined | unknown, - context: CreateNodesContext, - ) => { - const root = dirname(projectConfigurationFile); - const projectConfiguration: ProjectConfiguration = readJsonFile( - projectConfigurationFile, - ); - - const { - publishableTags = ['publishable'], - tsconfig = 'tools/tsconfig.tools.json', - publishScript = PUBLISH_SCRIPT, - bumpScript = BUMP_SCRIPT, - directory = projectConfiguration?.targets?.build?.options?.outputPath ?? - process.cwd(), - verbose = false, - } = (opts ?? {}) as CreateNodesOptions; - const isPublishable = (projectConfiguration?.tags ?? []).some(target => - publishableTags.includes(target), - ); - if (!isPublishable) { - return {}; - } - - const { name: projectName } = projectConfiguration; - return { - projects: { - [root]: { - targets: publishTargets({ - tsconfig, - publishScript, - bumpScript, - directory, - projectName, - verbose, - }), - }, - }, - }; - }, -]; - -function publishTargets({ - tsconfig, - publishScript, - bumpScript, - directory, - projectName, - verbose, -}: Required> & { - projectName: string; -}) { - return { - publish: { - command: `tsx --tsconfig={args.tsconfig} {args.script} --projectName=${projectName} --directory=${directory} --registry={args.registry} --userconfig={args.userconfig} --nextVersion={args.nextVersion} --tag={args.tag} --verbose=${verbose}`, - options: { - script: publishScript, - tsconfig, - }, - }, - 'publish-e2e': { - dependsOn: [ - 'build', - { - target: 'publish-e2e', - projects: 'dependencies', - params: 'forward', - }, - ], - command: `tsx --tsconfig={args.tsconfig} {args.script} --projectName=${projectName} --directory=${directory} --registry={args.registry} --userconfig={args.userconfig} --nextVersion={args.nextVersion} --tag={args.tag} --verbose=${verbose}`, - options: { - script: publishScript, - tsconfig, - }, - }, - 'bump-version': { - dependsOn: ['build'], - command: `tsx --tsconfig={args.tsconfig} {args.script} --directory=${directory} --nextVersion={args.nextVersion} --verbose=${verbose}`, - options: { - script: bumpScript, - tsconfig, - }, - }, - }; -} diff --git a/tools/src/publish/types.ts b/tools/src/publish/types.ts deleted file mode 100644 index c1c9d9426..000000000 --- a/tools/src/publish/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -export type PublishOptions = { - projectName?: string; - directory?: string; - userconfig?: string; - registry?: string; - tag?: string; - nextVersion: string; - verbose?: boolean; - parallel?: number; -}; -export type BumpOptions = { - nextVersion: string; - verbose?: boolean; - directory?: string; -}; diff --git a/tools/src/publish/utils.ts b/tools/src/publish/utils.ts deleted file mode 100644 index c2a84f08f..000000000 --- a/tools/src/publish/utils.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { execFileSync, execSync } from 'node:child_process'; -import { join } from 'node:path'; -import { objectToCliArgs } from '../../../packages/utils/src'; -import { BUMP_SCRIPT } from './constants'; -import type { PublishOptions } from './types'; - -export function nxRunManyPublish({ - registry, - tag = 'e2e', - nextVersion, - userconfig, - parallel, -}: PublishOptions) { - console.info(`Publish packages to registry: ${registry}.`); - try { - execFileSync( - 'npx', - [ - 'nx', - 'run-many', - '--targets=publish', - ...(parallel ? [`--parallel=${parallel}`] : []), - '--', - ...(nextVersion ? [`--nextVersion=${nextVersion}`] : []), - ...(tag ? [`--tag=${tag}`] : []), - ...(registry ? [`--registry=${registry}`] : []), - ...(userconfig ? [`--userconfig=${userconfig}`] : []), - ], - { env: process.env, stdio: 'inherit', shell: true }, - ); - } catch (error) { - console.error('Error publishing packages:\n' + error.message); - throw error; - } -} - -export function findLatestVersion(): string { - const version = execSync('git describe --tags --abbrev=0') - .toString() - .trim() - .replace(/^v/, ''); - console.info(`Version from "git describe --tags --abbrev=0": ${version}`); - return version; -} - -export function bumpVersion({ - nextVersion, - cwd, -}: { - nextVersion: string; - cwd: string; -}) { - try { - return execSync( - `tsx ${join(process.cwd(), BUMP_SCRIPT)} ${objectToCliArgs({ - nextVersion, - }).join(' ')}`, - { cwd }, - ).toString(); - } catch (error) { - console.error('Error bumping package version.'); - throw error; - } -} - -export function nxBumpVersion({ - projectName, - nextVersion, - directory, -}: { - projectName: string; - nextVersion: string; - directory: string; -}) { - try { - execSync( - `nx bump-version ${objectToCliArgs({ - _: projectName, - nextVersion, - directory, - }).join(' ')}`, - {}, - ).toString(); - } catch (error) { - console.error('Error Nx bump-version target failed.'); - throw error; - } -} diff --git a/tools/src/verdaccio/README.md b/tools/src/verdaccio/README.md deleted file mode 100644 index 2c73d9c7b..000000000 --- a/tools/src/verdaccio/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# TODO - -- in the future the root project has no verdaccio target anymore. only e2e projects have a verdaccio target. -- Use `.npmrc` form e2e folder -- move uniquePort into project targets of e2e projects - -https://verdaccio.org/docs/setup-npm#specific - -- Add post target - -```jsonc -'post-registry': { - dependsOn: [ - ...targets.map(target => ({ - projects: 'self', - target, - })), - ], - command: `echo POST E2E - stop verdaccio on port ${port}`, - }, -``` - -- Move verdaccio targets into every project that has a e2e target - -```typescript - - const hasPreVerdaccioTargets = someTargetsPresent(projectConfiguration?.targets ?? {}, preTargets); - if (!hasPreVerdaccioTargets) { - return {}; - } - -``` - -# Verdaccio Nx Plugin - -A Nx plugin that adds targets that help to test packages published to a package registry like NPM. -It uses Verdaccio to start a local registry and test the package against it. -This helps to catch tricky bugs before publishing the package to the public registry. - -## Usage - -Register the plugin in your `nx.json` - -```jsonc -// nx.json -{ - "name": "my-project", - "plugins": ["tools/verdaccio/verdaccio.plugin.ts"], -} -``` - -### Options - -You can configure the plugin by providing a options object in addition to the plugin path - -| Name | Type | Default | Description | -| ---------- | --------- | ------------------------------- | ------------------------------------------ | -| `verbose` | `boolean` | `false` | Log additional information. | -| `tsconfig` | `string` | `tools/tsconfig.tools.json` | The tsconfig file to use. | -| `port` | `number` | `4873` | The port to start the Verdaccio server on. | -| `config` | `string` | `tools/verdaccio/verdaccio.yml` | The Verdaccio configuration file. | -| `storage` | `string` | `tools/verdaccio/storage` | The storage directory for Verdaccio. | - -Example: - -```jsonc -// nx.json -{ - //... - "plugins": [ - { - "plugin": "tools/verdaccio/verdaccio.plugin.ts", - "options": { - "tsconfig": "tools/tsconfig.tools.json", - "verbose": true, - }, - }, - ], -} -``` - -### Targets - -> [!NOTE] -> By default target are only added to projects with a `e2e` target configured. - -#### `start-verdaccio` - -Starts a Verdaccio server. - -It will automatically use `tools/tsconfig.tools.ts` to execute the script as well as derives the package name from the project name from `package/root/package.json`. -By default, it registers the latest version of the package from the default registry. - -Run: -`nx run :start-verdaccio` - -**Options:** - -| Name | Type | Default | Description | -| ---------- | -------- | ------------------------------- | -------------------------------------------------- | -| `registry` | `string` | `https://registry.npmjs.org` | The registry to check the package version against. | -| `config` | `string` | `tools/verdaccio/verdaccio.yml` | The Verdaccio configuration file. | -| `port` | `number` | generated uniquePort | The port to start the Verdaccio server on. | -| `storage` | `string` | `tmp/e2e/storage ` | The storage directory for Verdaccio. | diff --git a/tools/src/verdaccio/bin/create-verdaccio-env.ts b/tools/src/verdaccio/bin/create-verdaccio-env.ts deleted file mode 100644 index 130f1535f..000000000 --- a/tools/src/verdaccio/bin/create-verdaccio-env.ts +++ /dev/null @@ -1,21 +0,0 @@ -import yargs, { Options } from 'yargs'; -import { hideBin } from 'yargs/helpers'; -import { - type StartVerdaccioAndSetupEnvOptions, - nxStartVerdaccioAndSetupEnv, -} from '../env'; - -const argv = yargs(hideBin(process.argv)) - .version(false) - .options({ - verbose: { type: 'boolean' }, - projectName: { type: 'string', demandOption: true }, - port: { type: 'string' }, - } satisfies Partial< - Record - >).argv; - -(async () => { - await nxStartVerdaccioAndSetupEnv(argv as StartVerdaccioAndSetupEnvOptions); - process.exit(0); -})(); diff --git a/tools/src/verdaccio/constants.ts b/tools/src/verdaccio/constants.ts deleted file mode 100644 index f1d75171f..000000000 --- a/tools/src/verdaccio/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { join } from 'node:path'; - -export const START_VERDACCIO_SERVER_TARGET_NAME = 'start-verdaccio-server'; -export const START_VERDACCIO_ENV_TARGET_NAME = 'start-verdaccio-env'; -export const STOP_VERDACCIO_TARGET_NAME = 'stop-verdaccio'; -export const DEFAULT_VERDACCIO_STORAGE = join('tmp', 'verdaccio', 'storage'); -export const DEFAULT_VERDACCIO_CONFIG = join('.verdaccio', 'config.yml'); diff --git a/tools/src/verdaccio/env.ts b/tools/src/verdaccio/env.ts deleted file mode 100644 index e164064cc..000000000 --- a/tools/src/verdaccio/env.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { bold, gray, red } from 'ansis'; -// eslint-disable-next-line n/no-sync -import { execFileSync, execSync } from 'node:child_process'; -import { join } from 'node:path'; -// can't import from utils -import { objectToCliArgs } from '../../../packages/nx-plugin/src/executors/internal/cli'; -import { - setupTestFolder, - teardownTestFolder, -} from '../../../testing/test-setup/src'; -import { ensureDirectoryExists } from '../../../testing/test-utils/src'; -import { - NxStarVerdaccioOptions, - Registry, - nxStartVerdaccioServer, -} from './registry'; - -export function projectE2eScope(projectName: string): string { - return join('tmp', 'e2e', projectName); -} - -export type VerdaccioEnv = { - workspaceRoot: string; - userconfig?: string; -}; - -export function configureRegistry( - { - url, - urlNoProtocol, - userconfig, - }: Registry & Pick, - verbose?: boolean, -) { - /** - * Protocol-Agnostic Configuration: The use of // allows NPM to configure authentication for a registry without tying it to a specific protocol (http: or https:). - * This is particularly useful when the registry might be accessible via both HTTP and HTTPS. - * - * Example: //registry.npmjs.org/:_authToken=your-token - */ - const token = 'secretVerdaccioToken'; - const setAuthToken = `npm config set ${urlNoProtocol}/:_authToken "${token}" ${objectToCliArgs( - { userconfig }, - ).join(' ')}`; - if (verbose) { - console.info( - `${gray('>')} ${gray(bold('Verdaccio-Env'))} Execute: ${setAuthToken}`, - ); - } - execSync(setAuthToken); - - const setRegistry = `npm config set registry="${url}" ${objectToCliArgs({ - userconfig, - }).join(' ')}`; - if (verbose) { - console.info( - `${gray('>')} ${gray(bold('Verdaccio-Env'))} Execute: ${userconfig}`, - ); - } - execSync(setRegistry); -} - -export function unconfigureRegistry( - { urlNoProtocol }: Pick, - verbose?: boolean, -) { - execSync(`npm config delete registry`); - execSync(`npm config delete ${urlNoProtocol}/:_authToken`); - if (verbose) { - console.info(`${gray('>')} ${gray(bold('Verdaccio-Env'))} delete registry`); - console.info( - `${gray('>')} ${gray( - bold('Verdaccio-Env'), - )} delete npm authToken: ${urlNoProtocol}`, - ); - } -} - -export async function setupNpmWorkspace(directory: string, verbose?: boolean) { - if (verbose) { - console.info( - `${gray('>')} ${gray( - bold('Verdaccio-Env'), - )} Execute: npm init in directory ${directory}`, - ); - } - const cwd = process.cwd(); - await ensureDirectoryExists(directory); - process.chdir(join(cwd, directory)); - try { - execFileSync('npm', ['init', '--force']).toString(); - } catch (error) { - console.error( - `${red('>')} ${red( - bold('Verdaccio-Env'), - )} Error creating NPM workspace: ${(error as Error).message}`, - ); - } finally { - process.chdir(cwd); - } -} - -export type StartVerdaccioAndSetupEnvOptions = Partial< - NxStarVerdaccioOptions & VerdaccioEnv -> & - Pick; - -export type VerdaccioEnvResult = VerdaccioEnv & { - registry: Registry; - stop: () => void; -}; - -export async function nxStartVerdaccioAndSetupEnv({ - projectName, - port, - verbose = false, - workspaceRoot = projectE2eScope(projectName), - location = 'none', - // reset or remove cached packages and/or metadata. - clear = true, -}: StartVerdaccioAndSetupEnvOptions): Promise { - // set up NPM workspace environment - const storage = join(workspaceRoot, 'storage'); - - // @TODO potentially done by verdaccio task when clearing storage - await setupTestFolder(storage); - const registryResult = await nxStartVerdaccioServer({ - projectName, - storage, - port, - location, - clear, - verbose, - }); - - await setupNpmWorkspace(workspaceRoot, verbose); - - const userconfig = join(workspaceRoot, '.npmrc'); - configureRegistry({ ...registryResult.registry, userconfig }, verbose); - - return { - ...registryResult, - stop: () => { - registryResult.stop(); - unconfigureRegistry(registryResult.registry, verbose); - }, - workspaceRoot, - userconfig, - } satisfies VerdaccioEnvResult; -} - -export async function nxStopVerdaccioAndTeardownEnv( - result: VerdaccioEnvResult, -) { - if (result) { - const { stop, registry, workspaceRoot } = result; - if (stop == null) { - throw new Error( - 'global e2e teardown script was not able to derive the stop script for the active registry from "activeRegistry"', - ); - } - console.info(`Un configure registry: ${registry.url}`); - if (typeof stop === 'function') { - stop(); - } else { - console.error('Stop is not a function. Type:', typeof stop); - } - await teardownTestFolder(workspaceRoot); - } else { - throw new Error(`Failed to stop registry.`); - } -} diff --git a/tools/src/verdaccio/registry.ts b/tools/src/verdaccio/registry.ts deleted file mode 100644 index 3afe3788f..000000000 --- a/tools/src/verdaccio/registry.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { bold, gray, red } from 'ansis'; -import { executeProcess } from '@code-pushup/utils'; -// can't import from utils -import { objectToCliArgs } from '../../../packages/nx-plugin'; -import { teardownTestFolder } from '../../../testing/test-setup/src'; -import { killProcesses, listProcess } from '../debug/utils'; -import { START_VERDACCIO_SERVER_TARGET_NAME } from './constants'; - -export function uniquePort(): number { - return Number((6000 + Number(Math.random() * 1000)).toFixed(0)); -} - -export type RegistryServer = { - protocol: string; - port: string | number; - host: string; - urlNoProtocol: string; - url: string; -}; -export type Registry = RegistryServer & - Required>; - -export type RegistryResult = { - registry: Registry; - stop: () => void; -}; - -export function parseRegistryData(stdout: string): RegistryServer { - const output = stdout.toString(); - - // Extract protocol, host, and port - const match = output.match( - /(?https?):\/\/(?[^:]+):(?\d+)/, - ); - - if (!match?.groups) { - throw new Error('Could not parse registry data from stdout'); - } - - const protocol = match.groups['proto']; - if (!protocol || !['http', 'https'].includes(protocol)) { - throw new Error( - `Invalid protocol ${protocol}. Only http and https are allowed.`, - ); - } - const host = match.groups['host']; - if (!host) { - throw new Error(`Invalid host ${String(host)}.`); - } - const port = !Number.isNaN(Number(match.groups['port'])) - ? Number(match.groups['port']) - : undefined; - if (!port) { - throw new Error(`Invalid port ${String(port)}.`); - } - return { - protocol, - host, - port, - urlNoProtocol: `//${host}:${port}`, - url: `${protocol}://${host}:${port}`, - }; -} - -export type NxStarVerdaccioOnlyOptions = { - projectName?: string; - verbose?: boolean; -}; - -export type VerdaccioExecuterOptions = { - storage?: string; - port?: string; - p?: string; - config?: string; - c?: string; - location: string; - // reset or remove cached packages and/or metadata. - clear: boolean; -}; - -export type NxStarVerdaccioOptions = VerdaccioExecuterOptions & - NxStarVerdaccioOnlyOptions; - -export async function nxStartVerdaccioServer({ - projectName = '', - storage, - port, - location, - clear, - verbose = false, -}: NxStarVerdaccioOptions): Promise { - let startDetected = false; - - const positionalArgs = [ - 'exec', - 'nx', - START_VERDACCIO_SERVER_TARGET_NAME, - projectName ?? '', - '--', - ]; - const args = objectToCliArgs< - Partial< - VerdaccioExecuterOptions & { _: string[]; verbose: boolean; cwd: string } - > - >({ - _: positionalArgs, - storage, - port, - verbose, - location, - clear, - }); - - // a link to the process started by this command, not one of the child processes. (every port is spawned by a command) - const commandId = positionalArgs.join(' '); - - if (verbose) { - console.info( - `${gray('>')} ${gray( - bold('Verdaccio'), - )} Start server with command: ${commandId}`, - ); - } - - return ( - new Promise((resolve, reject) => { - executeProcess({ - command: 'npm', - args, - shell: true, - observer: { - onStdout: (stdout: string) => { - if (verbose) { - process.stdout.write( - `${gray('>')} ${gray(bold('Verdaccio'))} ${stdout}`, - ); - } - - // Log of interest: warn --- http address - http://localhost:/ - verdaccio/5.31.1 - if (!startDetected && stdout.includes('http://localhost:')) { - // only setup env one time - startDetected = true; - - const result: RegistryResult = { - registry: { - storage, - ...parseRegistryData(stdout), - }, - // https://verdaccio.org/docs/cli/#default-database-file-location - stop: () => { - // this makes the process throw - killProcesses({ commandMatch: commandId }); - }, - }; - - console.info( - `${gray('>')} ${gray( - bold('Verdaccio'), - )} Registry started on URL: ${bold( - result.registry.url, - )}, with PID: ${bold( - listProcess({ commandMatch: commandId }).at(0)?.pid, - )}`, - ); - if (verbose) { - console.info(`${gray('>')} ${gray(bold('Verdaccio'))}`); - console.table(result); - } - - resolve(result); - } - }, - onStderr: (stderr: string) => { - if (verbose) { - process.stdout.write( - `${red('>')} ${red(bold('Verdaccio'))} ${stderr}`, - ); - } - }, - }, - }) - // @TODO reconsider this error handling - .catch(error => { - if (error.message !== 'Failed to start verdaccio: undefined') { - console.error( - `${red('>')} ${red( - bold('Verdaccio'), - )} Error starting ${projectName} verdaccio registry:\n${error}`, - ); - } else { - reject(error); - } - }); - }) - // in case the server dies unexpectedly clean folder - .catch((error: unknown) => { - teardownTestFolder(storage); - throw error; - }) - ); -} diff --git a/tools/src/verdaccio/start-local-registry.ts b/tools/src/verdaccio/start-local-registry.ts deleted file mode 100644 index 5df3d8664..000000000 --- a/tools/src/verdaccio/start-local-registry.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * This script starts a local registry for e2e testing purposes. - * It is meant to be called in jest's globalSetup. - */ -import { execSync } from 'child_process'; -import { executeProcess } from '../../../packages/utils/src'; -import { - configureRegistry, - parseRegistryData, - unconfigureRegistry, -} from './utils'; - -export type RegistryOptions = { - // local registry target to run - localRegistryTarget: string; - // storage folder for the local registry - storage?: string; - verbose?: boolean; - port?: number; -}; - -export type RegistryData = { - protocol: string; - port: string | number; - host: string; - registryNoProtocol: string; - registry: string; -}; - -export type RegistryResult = { - registryData: RegistryData; - stop: () => void; -}; -export default async function startLocalRegistry({ - localRegistryTarget, - storage, - verbose, - port, -}: RegistryOptions): Promise { - if (verbose) { - console.info(`Set up registry for port: ${port}.`); - } - - return new Promise((resolve, reject) => { - executeProcess({ - command: 'npx', - args: [ - 'nx', - 'run', - localRegistryTarget, - '--', - '--location none', - // reset or remove cached packages and or metadata. - '--clear true', - ...(storage ? [`--storage=${storage}`] : []), - ...(port ? [`--port${String(port)}`] : []), - ...(verbose ? [`--verbose`] : []), - ], - // @TODO understand what it does - // stdio: 'pipe', - shell: true, - observer: { - onStdout: (data, childProcess) => { - if (verbose) { - process.stdout.write(data); - } - - if (data.toString().includes('//localhost:')) { - const registryData = parseRegistryData(data); - - configureRegistry(registryData); - - if (verbose) { - console.info('Registry started:'); - console.table(registryData); - } - - resolve({ - registryData, - stop: () => { - // this makes the process throw - childProcess?.kill(); - unconfigureRegistry(registryData); - execSync( - `npm config delete ${registryData.registryNoProtocol}/:_authToken`, - ); - }, - }); - } - }, - onStderr: data => { - if (verbose) { - process.stdout.write(data); - } - }, - }, - }).catch(error => { - if (error.message !== 'Failed to start verdaccio: undefined') { - reject(error); - } else { - reject({ - registryData: { port }, - stop: () => { - console.log('Stop from executeProcess error' + error.message); - }, - }); - } - }); - }); -} diff --git a/tools/src/verdaccio/stop-local-registry.ts b/tools/src/verdaccio/stop-local-registry.ts deleted file mode 100644 index 920f19fc6..000000000 --- a/tools/src/verdaccio/stop-local-registry.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * This script stops the local registry for e2e testing purposes. - * It is meant to be called in jest's globalTeardown. - */ - -export default (stopLocalRegistry?: () => void, registry?: string) => { - if (stopLocalRegistry == null) { - throw new Error( - 'global e2e teardown script was not able to derive the stop script for the active registry from "activeRegistry"', - ); - } - console.info(`Stop local registry: ${registry}`); - if (typeof stopLocalRegistry === 'function') { - stopLocalRegistry(); - } else { - console.log('WRONG stopLocalRegistry: ', stopLocalRegistry); - } -}; diff --git a/tools/src/verdaccio/utils.ts b/tools/src/verdaccio/utils.ts deleted file mode 100644 index f72734b2c..000000000 --- a/tools/src/verdaccio/utils.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { execSync } from 'node:child_process'; -import type { RegistryData } from './start-local-registry'; - -export function uniquePort(): number { - return Number((6000 + Number(Math.random() * 1000)).toFixed(0)); -} - -export function configureRegistry({ - host, - registry, - registryNoProtocol, -}: RegistryData) { - console.info(`Set NPM registry under location user to ${registry}`); - execSync(`npm config set registry "${registry}"`); - - console.info(`Set yarn whitelΓ­st process.env`); - - /** - * Protocol-Agnostic Configuration: The use of // allows NPM to configure authentication for a registry without tying it to a specific protocol (http: or https:). - * This is particularly useful when the registry might be accessible via both HTTP and HTTPS. - * - * Example: //registry.npmjs.org/:_authToken=your-token - */ - const token = 'secretVerdaccioToken'; - execSync(`npm config set ${registryNoProtocol}/:_authToken "${token}"`); - console.info(`_authToken for ${registry} set to ${token}`); -} - -export function unconfigureRegistry({ - registryNoProtocol, -}: Pick) { - execSync('npm config delete registry'); - execSync(`npm config delete ${registryNoProtocol}/:_authToken`); - console.info('delete npm authToken: ' + registryNoProtocol); -} - -export function parseRegistryData(stdout: string): RegistryData { - const port = parseInt( - stdout.toString().match(/localhost:(?\d+)/)?.groups?.port ?? '', - ); - if (isNaN(port)) { - throw new Error('Invalid port number'); - } - const protocolMatch = stdout - .toString() - .match(/(?https?):\/\/localhost:/); - const protocol = protocolMatch?.groups?.proto; - - if (!protocol || !['http', 'https'].includes(protocol)) { - throw new Error( - `Invalid protocol ${protocol}. Only http and https are allowed.`, - ); - } - - const host = 'localhost'; - const registryNoProtocol = `//${host}:${port}`; - const registry = `${protocol}:${registryNoProtocol}`; - - return { - protocol, - host, - port, - registryNoProtocol, - registry, - }; -} diff --git a/tools/src/verdaccio/verdaccio.plugin.ts b/tools/src/verdaccio/verdaccio.plugin.ts deleted file mode 100644 index f1cf6ae53..000000000 --- a/tools/src/verdaccio/verdaccio.plugin.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { - type CreateNodes, - type CreateNodesContext, - readJsonFile, -} from '@nx/devkit'; -import { bold } from 'ansis'; -import { dirname } from 'node:path'; -import type { ProjectConfiguration } from 'nx/src/config/workspace-json-project-json'; -import { someTargetsPresent } from '../utils'; -import { START_VERDACCIO_SERVER_TARGET_NAME } from './constants'; -import { uniquePort } from './utils'; - -type CreateNodesOptions = { - port?: string | number; - config?: string; - storage?: string; - preTargets?: string | string[]; - verbose?: boolean; -}; - -export const createNodes: CreateNodes = [ - '**/project.json', - ( - projectConfigurationFile: string, - opts: undefined | unknown, - context: CreateNodesContext, - ) => { - const { - port = uniquePort(), - config = '.verdaccio/config.yml', - storage = 'tmp/local-registry/storage', - verbose = false, - preTargets = ['e2e'], - } = (opts ?? {}) as CreateNodesOptions; - const { workspaceRoot } = context; - const root = dirname(projectConfigurationFile); - const projectConfiguration: ProjectConfiguration = readJsonFile( - projectConfigurationFile, - ); - - const hasPreVerdaccioTargets = someTargetsPresent( - projectConfiguration.targets ?? {}, - preTargets, - ); - const isRootProject = root === '.'; - if (!hasPreVerdaccioTargets && !isRootProject) { - return {}; - } - - if (!projectConfiguration.implicitDependencies && !isRootProject) { - throw new Error( - `You have to specify the needed projects as implicitDependencies in ${bold( - projectConfiguration.name, - )} to have them set up.`, - ); - } - - return { - projects: { - [root]: { - targets: verdaccioTargets({ - port, - config, - storage, - preTargets, - deps: projectConfiguration.implicitDependencies, - }), - }, - }, - }; - }, -]; - -function verdaccioTargets({ - port, - config, - storage, - deps, -}: Required> & { deps: string[] }) { - return { - [START_VERDACCIO_SERVER_TARGET_NAME]: { - executor: '@nx/js:verdaccio', - options: { - port, - config, - storage, - }, - }, - ['setup-deps']: { - executor: 'nx:run-commands', - options: { - commands: [ - `nx run-many -t publish -p ${deps?.join(',')}`, - // NPM install needs to be run sequentially as it can cause issues with installing dependencies multiple times - `nx run-many -t npm-install -p ${deps?.join(',')} --parallel=1`, - ], - parallel: false, - }, - }, - }; -}