diff --git a/nx.json b/nx.json index c5d23e1ca..7b8e4fec6 100644 --- a/nx.json +++ b/nx.json @@ -2,7 +2,11 @@ "$schema": "./node_modules/nx/schemas/nx-schema.json", "namedInputs": { "default": ["{projectRoot}/**/*", "sharedGlobals"], - "os": [{ "runtime": "node -e \"console.log(require('os').platform())\"" }], + "os": [ + { + "runtime": "node -e \"console.log(require('os').platform())\"" + } + ], "production": [ "default", "!{projectRoot}/README.md", @@ -25,23 +29,42 @@ ], "test-vitest-inputs": [ "os", - { "env": "NX_VERBOSE_LOGGING" }, - { "externalDependencies": ["vitest"] } + { + "env": "NX_VERBOSE_LOGGING" + }, + { + "externalDependencies": ["vitest"] + } ], "lint-eslint-inputs": [ "{workspaceRoot}/eslint.config.js", - { "externalDependencies": ["eslint"] } + { + "externalDependencies": ["eslint"] + } ], "typecheck-typescript-inputs": [ "{workspaceRoot}/tsconfig.base.json", - { "externalDependencies": ["typescript"] } + { + "externalDependencies": ["typescript"] + } ], "code-pushup-inputs": [ "{workspaceRoot}/code-pushup.preset.ts", - { "env": "NODE_OPTIONS" }, - { "env": "TSX_TSCONFIG_PATH" } + { + "env": "NODE_OPTIONS" + }, + { + "env": "TSX_TSCONFIG_PATH" + } ], - "sharedGlobals": [{ "runtime": "node -v" }, { "runtime": "npm -v" }] + "sharedGlobals": [ + { + "runtime": "node -v" + }, + { + "runtime": "npm -v" + } + ] }, "targetDefaults": { "lint": { @@ -123,18 +146,17 @@ }, "code-pushup": { "cache": false, - "executor": "nx:run-commands", "dependsOn": ["code-pushup-*"], "options": { - "command": "node packages/cli/src/index.ts", - "args": [ - "--config={projectRoot}/code-pushup.config.ts", - "--cache.read", - "--persist.outputDir=.code-pushup/{projectName}" - ], - "env": { - "NODE_OPTIONS": "--import tsx", - "TSX_TSCONFIG_PATH": "tsconfig.base.json" + "config": "{projectRoot}/code-pushup.config.ts", + "persist": { + "outputDir": ".code-pushup/{projectName}" + } + }, + "configurations": { + "print-config": { + "command": "print-config", + "output": "{projectRoot}/.code-pushup/code-pushup.config.json" } } }, @@ -145,19 +167,12 @@ "outputs": [ "{workspaceRoot}/.code-pushup/{projectName}/coverage/runner-output.json" ], - "executor": "nx:run-commands", "options": { - "command": "node packages/cli/src/index.ts collect", - "args": [ - "--config={projectRoot}/code-pushup.config.ts", - "--cache.write", - "--onlyPlugins=coverage", - "--persist.skipReports=true", - "--persist.outputDir=.code-pushup/{projectName}" - ], - "env": { - "NODE_OPTIONS": "--import tsx", - "TSX_TSCONFIG_PATH": "tsconfig.base.json" + "command": "collect", + "config": "{projectRoot}/code-pushup.config.ts", + "onlyPlugins": ["coverage"], + "persist": { + "outputDir": ".code-pushup/{projectName}" } } }, @@ -168,24 +183,17 @@ "outputs": [ "{workspaceRoot}/.code-pushup/{projectName}/eslint/runner-output.json" ], - "executor": "nx:run-commands", "options": { - "command": "node packages/cli/src/index.ts collect", - "args": [ - "--config={projectRoot}/code-pushup.config.ts", - "--cache.write", - "--onlyPlugins=eslint", - "--persist.skipReports", - "--persist.outputDir=.code-pushup/{projectName}" - ], - "env": { - "NODE_OPTIONS": "--import tsx", - "TSX_TSCONFIG_PATH": "tsconfig.base.json" + "command": "collect", + "config": "{projectRoot}/code-pushup.config.ts", + "onlyPlugins": ["eslint"], + "persist": { + "outputDir": ".code-pushup/{projectName}" } } }, "code-pushup-js-packages": { - "cache": false, + "cache": true, "inputs": [ { "runtime": "date +%Y-%m-%d" @@ -194,17 +202,12 @@ "outputs": [ "{workspaceRoot}/.code-pushup/{projectName}/js-packages/runner-output.json" ], - "executor": "nx:run-commands", "options": { - "command": "node packages/cli/src/index.ts collect", - "args": [ - "--config={projectRoot}/code-pushup.config.ts", - "--onlyPlugins=js-packages", - "--persist.outputDir=.code-pushup/{projectName}" - ], - "env": { - "NODE_OPTIONS": "--import tsx", - "TSX_TSCONFIG_PATH": "tsconfig.base.json" + "command": "collect", + "config": "{projectRoot}/code-pushup.config.ts", + "onlyPlugins": ["js-packages"], + "persist": { + "outputDir": ".code-pushup/{projectName}" } } }, @@ -214,19 +217,12 @@ "outputs": [ "{workspaceRoot}/.code-pushup/{projectName}/lighthouse/runner-output.json" ], - "executor": "nx:run-commands", "options": { - "command": "node packages/cli/src/index.ts collect", - "args": [ - "--config={projectRoot}/code-pushup.config.ts", - "--cache.write", - "--onlyPlugins=lighthouse", - "--persist.skipReports", - "--persist.outputDir=.code-pushup/{projectName}" - ], - "env": { - "NODE_OPTIONS": "--import tsx", - "TSX_TSCONFIG_PATH": "tsconfig.base.json" + "command": "collect", + "config": "{projectRoot}/code-pushup.config.ts", + "onlyPlugins": ["lighthouse"], + "persist": { + "outputDir": ".code-pushup/{projectName}" } } }, @@ -240,19 +236,12 @@ "outputs": [ "{workspaceRoot}/.code-pushup/{projectName}/jsdocs/runner-output.json" ], - "executor": "nx:run-commands", "options": { - "command": "node packages/cli/src/index.ts collect", - "args": [ - "--config={projectRoot}/code-pushup.config.ts", - "--cache.write", - "--onlyPlugins=jsdocs", - "--persist.skipReports", - "--persist.outputDir=.code-pushup/{projectName}" - ], - "env": { - "NODE_OPTIONS": "--import tsx", - "TSX_TSCONFIG_PATH": "tsconfig.base.json" + "command": "collect", + "config": "{projectRoot}/code-pushup.config.ts", + "onlyPlugins": ["jsdocs"], + "persist": { + "outputDir": ".code-pushup/{projectName}" } } }, @@ -266,19 +255,12 @@ "outputs": [ "{workspaceRoot}/.code-pushup/{projectName}/typescript/runner-output.json" ], - "executor": "nx:run-commands", "options": { - "command": "node packages/cli/src/index.ts collect", - "args": [ - "--config={projectRoot}/code-pushup.config.ts", - "--cache.write", - "--onlyPlugins=typescript", - "--persist.skipReports", - "--persist.outputDir=.code-pushup/{projectName}" - ], - "env": { - "NODE_OPTIONS": "--import tsx", - "TSX_TSCONFIG_PATH": "tsconfig.base.json" + "command": "collect", + "config": "{projectRoot}/code-pushup.config.ts", + "onlyPlugins": ["typescript"], + "persist": { + "outputDir": ".code-pushup/{projectName}" } } }, @@ -288,18 +270,12 @@ "outputs": [ "{workspaceRoot}/.code-pushup/{projectName}/axe/runner-output.json" ], - "executor": "nx:run-commands", "options": { - "command": "node packages/cli/src/index.ts collect", - "args": [ - "--config={projectRoot}/code-pushup.config.ts", - "--cache.write", - "--onlyPlugins=axe", - "--persist.outputDir=.code-pushup/{projectName}" - ], - "env": { - "NODE_OPTIONS": "--import tsx", - "TSX_TSCONFIG_PATH": "tsconfig.base.json" + "command": "collect", + "config": "{projectRoot}/code-pushup.config.ts", + "onlyPlugins": ["axe"], + "persist": { + "outputDir": ".code-pushup/{projectName}" } } }, @@ -310,12 +286,29 @@ "packageRoot": "{projectRoot}/dist", "registry": "https://registry.npmjs.org/" } + }, + "validate-cp-targets": { + "executor": "nx:run-commands", + "cache": false, + "options": { + "command": "node tools/scripts/validate-cp-targets.mjs" + } } }, "workspaceLayout": { "appsDir": "examples", "libsDir": "packages" }, + "pluginsConfig": { + "@code-pushup/nx-plugin": { + "projectPrefix": "cli", + "bin": "packages/cli/src/index.ts", + "env": { + "NODE_OPTIONS": "--import tsx", + "TSX_TSCONFIG_PATH": "tsconfig.base.json" + } + } + }, "generators": {}, "release": { "projects": ["packages/*"], @@ -344,6 +337,76 @@ "releaseTagPattern": "v{version}" }, "plugins": [ + { + "plugin": "@code-pushup/nx-plugin", + "options": { + "targetName": "code-pushup", + "projectPrefix": "cli" + }, + "exclude": ["tools/**", "testing/**", "examples/**"] + }, + { + "plugin": "@code-pushup/nx-plugin", + "options": { + "targetName": "code-pushup-coverage" + }, + "exclude": ["tools/**", "testing/**", "examples/**"] + }, + { + "plugin": "@code-pushup/nx-plugin", + "options": { + "targetName": "code-pushup-eslint" + }, + "exclude": ["testing/**", "examples/**"] + }, + { + "plugin": "@code-pushup/nx-plugin", + "options": { + "targetName": "code-pushup-typescript" + }, + "exclude": [ + "packages/models/**", + "packages/plugin-lighthouse", + "packages/plugin-js-packages", + "tools/**", + "testing/**", + "examples/**" + ] + }, + { + "plugin": "@code-pushup/nx-plugin", + "options": { + "targetName": "code-pushup-jsdocs" + }, + "exclude": [ + "packages/models", + "packages/plugin-lighthouse", + "tools/**", + "testing/**", + "examples/**" + ] + }, + { + "plugin": "@code-pushup/nx-plugin", + "options": { + "targetName": "code-pushup-js-packages" + }, + "exclude": ["packages/**", "tools/**", "testing/**", "examples/**"] + }, + { + "plugin": "@code-pushup/nx-plugin", + "options": { + "targetName": "code-pushup-lighthouse" + }, + "exclude": ["packages/**", "tools/**", "testing/**", "examples/**"] + }, + { + "plugin": "@code-pushup/nx-plugin", + "options": { + "targetName": "code-pushup-axe" + }, + "exclude": ["packages/**", "tools/**", "testing/**", "examples/**"] + }, { "plugin": "@push-based/nx-verdaccio", "options": { diff --git a/package-lock.json b/package-lock.json index deb4b547d..f5eccb0c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,11 @@ "license": "MIT", "dependencies": { "@axe-core/playwright": "^4.11.0", + "@code-pushup/cli": "^0.96.1", + "@code-pushup/models": "^0.96.1", + "@code-pushup/nx-plugin": "^0.96.1", "@code-pushup/portal-client": "^0.16.0", + "@code-pushup/utils": "^0.96.1", "@nx/devkit": "21.4.1", "@swc/helpers": "0.5.13", "ansis": "^3.3.2", @@ -2328,6 +2332,45 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/@code-pushup/cli": { + "version": "0.96.1", + "resolved": "https://registry.npmjs.org/@code-pushup/cli/-/cli-0.96.1.tgz", + "integrity": "sha512-HnRMzurJYMqAiGZc4pF1iOWTaLBB7DE2aXDDpD9BLFj7uijUprQLhU0N9ML4h8Nqeri0BnEI47a4MjxdyGrOcA==", + "license": "MIT", + "dependencies": { + "@code-pushup/core": "0.96.1", + "@code-pushup/models": "0.96.1", + "@code-pushup/utils": "0.96.1", + "ansis": "^3.3.0", + "simple-git": "^3.20.0", + "yargs": "^17.7.2" + }, + "bin": { + "code-pushup": "bin/index.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@code-pushup/core": { + "version": "0.96.1", + "resolved": "https://registry.npmjs.org/@code-pushup/core/-/core-0.96.1.tgz", + "integrity": "sha512-EomifNsgYYJ2ylKggz/eRXBGpnnqQ9SykggWp5wo1D+E02kec6cUZ8HlATvJfsRurc8W4REY7H1Ax02JXRjZhQ==", + "license": "MIT", + "dependencies": { + "@code-pushup/models": "0.96.1", + "@code-pushup/utils": "0.96.1", + "ansis": "^3.3.0" + }, + "peerDependencies": { + "@code-pushup/portal-client": "^0.16.0" + }, + "peerDependenciesMeta": { + "@code-pushup/portal-client": { + "optional": true + } + } + }, "node_modules/@code-pushup/eslint-config": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/@code-pushup/eslint-config/-/eslint-config-0.14.2.tgz", @@ -2412,6 +2455,29 @@ } } }, + "node_modules/@code-pushup/models": { + "version": "0.96.1", + "resolved": "https://registry.npmjs.org/@code-pushup/models/-/models-0.96.1.tgz", + "integrity": "sha512-dXRxMz+NDjky9/oUHt1jWzWeP0+a0AkamTxWZgja+ESAOSJgmqJdtuefq5+qZQHhlepmlz7znvE1Ri4lsuT7Xw==", + "license": "MIT", + "dependencies": { + "ansis": "^3.3.2", + "vscode-material-icons": "^0.1.0", + "zod": "^4.0.5" + } + }, + "node_modules/@code-pushup/nx-plugin": { + "version": "0.96.1", + "resolved": "https://registry.npmjs.org/@code-pushup/nx-plugin/-/nx-plugin-0.96.1.tgz", + "integrity": "sha512-X072zLK/xx6IXtYUT0Ik1v7NOAsgxf3R9Iqa/EWIXCvL1MSrMauHexT+lcCmebOgo9XOLx9spu1ffJq2hWh3cA==", + "license": "MIT", + "dependencies": { + "@code-pushup/models": "0.96.1", + "@code-pushup/utils": "0.96.1", + "@nx/devkit": ">=17.0.0", + "nx": ">=17.0.0" + } + }, "node_modules/@code-pushup/portal-client": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/@code-pushup/portal-client/-/portal-client-0.16.0.tgz", @@ -2424,6 +2490,28 @@ "vscode-material-icons": "^0.1.0" } }, + "node_modules/@code-pushup/utils": { + "version": "0.96.1", + "resolved": "https://registry.npmjs.org/@code-pushup/utils/-/utils-0.96.1.tgz", + "integrity": "sha512-u5XRkij66g5hzZZpNQUUfvW/bbMyUn2a2Ww5DNC3Mru6A6SOtdQlcrH3oZVLdDNO0rxhKHEgqvj76qyOIlTXWw==", + "license": "MIT", + "dependencies": { + "@code-pushup/models": "0.96.1", + "ansis": "^3.3.0", + "build-md": "^0.4.2", + "bundle-require": "^5.1.0", + "esbuild": "^0.25.2", + "ora": "^9.0.0", + "semver": "^7.6.0", + "simple-git": "^3.20.0", + "string-width": "^8.1.0", + "wrap-ansi": "^9.0.2", + "zod": "^4.0.5" + }, + "engines": { + "node": ">=17.0.0" + } + }, "node_modules/@commitlint/cli": { "version": "19.6.0", "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.6.0.tgz", diff --git a/package.json b/package.json index e4c55ca77..67b787912 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,11 @@ "private": true, "dependencies": { "@axe-core/playwright": "^4.11.0", + "@code-pushup/cli": "^0.96.1", + "@code-pushup/models": "^0.96.1", + "@code-pushup/nx-plugin": "^0.96.1", "@code-pushup/portal-client": "^0.16.0", + "@code-pushup/utils": "^0.96.1", "@nx/devkit": "21.4.1", "@swc/helpers": "0.5.13", "ansis": "^3.3.2", diff --git a/packages/ci/project.json b/packages/ci/project.json index 41fd8cda7..9215f87b3 100644 --- a/packages/ci/project.json +++ b/packages/ci/project.json @@ -7,12 +7,7 @@ "build": {}, "lint": {}, "unit-test": {}, - "int-test": {}, - "code-pushup": {}, - "code-pushup-eslint": {}, - "code-pushup-coverage": {}, - "code-pushup-typescript": {}, - "code-pushup-jsdocs": {} + "int-test": {} }, "tags": ["scope:tooling", "type:feature", "publishable"] } diff --git a/packages/cli/project.json b/packages/cli/project.json index e96eb2e2e..25666f226 100644 --- a/packages/cli/project.json +++ b/packages/cli/project.json @@ -11,12 +11,7 @@ }, "lint": {}, "unit-test": {}, - "int-test": {}, - "code-pushup": {}, - "code-pushup-eslint": {}, - "code-pushup-coverage": {}, - "code-pushup-typescript": {}, - "code-pushup-jsdocs": {} + "int-test": {} }, "tags": ["scope:core", "type:app", "publishable"] } diff --git a/packages/core/project.json b/packages/core/project.json index 6ffc72788..4924717c5 100644 --- a/packages/core/project.json +++ b/packages/core/project.json @@ -7,12 +7,7 @@ "build": {}, "lint": {}, "unit-test": {}, - "int-test": {}, - "code-pushup": {}, - "code-pushup-eslint": {}, - "code-pushup-coverage": {}, - "code-pushup-typescript": {}, - "code-pushup-jsdocs": {} + "int-test": {} }, "tags": ["scope:core", "type:feature", "publishable"] } diff --git a/packages/create-cli/project.json b/packages/create-cli/project.json index 8e4af8b5b..3e7597a1f 100644 --- a/packages/create-cli/project.json +++ b/packages/create-cli/project.json @@ -6,13 +6,7 @@ "targets": { "build": {}, "lint": {}, - - "unit-test": {}, - "code-pushup": {}, - "code-pushup-eslint": {}, - "code-pushup-coverage": {}, - "code-pushup-typescript": {}, - "code-pushup-jsdocs": {} + "unit-test": {} }, "tags": ["scope:tooling", "type:app", "publishable"] } diff --git a/packages/models/project.json b/packages/models/project.json index b09523d29..d1806c032 100644 --- a/packages/models/project.json +++ b/packages/models/project.json @@ -25,11 +25,7 @@ ] }, "lint": {}, - "unit-test": {}, - "code-pushup": {}, - "code-pushup-eslint": {}, - "code-pushup-coverage": {}, - "code-pushup-jsdocs": {} + "unit-test": {} }, "tags": ["scope:shared", "type:util", "publishable"] } diff --git a/packages/nx-plugin/project.json b/packages/nx-plugin/project.json index 247770900..63e15c1d4 100644 --- a/packages/nx-plugin/project.json +++ b/packages/nx-plugin/project.json @@ -51,12 +51,7 @@ } }, "unit-test": {}, - "int-test": {}, - "code-pushup": {}, - "code-pushup-eslint": {}, - "code-pushup-coverage": {}, - "code-pushup-typescript": {}, - "code-pushup-jsdocs": {} + "int-test": {} }, "tags": ["scope:tooling", "type:feature", "publishable"] } diff --git a/packages/plugin-axe/project.json b/packages/plugin-axe/project.json index 0b42d7f05..9929adaca 100644 --- a/packages/plugin-axe/project.json +++ b/packages/plugin-axe/project.json @@ -9,11 +9,6 @@ "build": {}, "lint": {}, "unit-test": {}, - "int-test": {}, - "code-pushup": {}, - "code-pushup-eslint": {}, - "code-pushup-coverage": {}, - "code-pushup-typescript": {}, - "code-pushup-jsdocs": {} + "int-test": {} } } diff --git a/packages/plugin-coverage/project.json b/packages/plugin-coverage/project.json index 04a76ed34..edb62346f 100644 --- a/packages/plugin-coverage/project.json +++ b/packages/plugin-coverage/project.json @@ -7,12 +7,7 @@ "build": {}, "lint": {}, "unit-test": {}, - "int-test": {}, - "code-pushup": {}, - "code-pushup-eslint": {}, - "code-pushup-coverage": {}, - "code-pushup-typescript": {}, - "code-pushup-jsdocs": {} + "int-test": {} }, "tags": ["scope:plugin", "type:feature", "publishable"] } diff --git a/packages/plugin-eslint/project.json b/packages/plugin-eslint/project.json index beabbc297..241101850 100644 --- a/packages/plugin-eslint/project.json +++ b/packages/plugin-eslint/project.json @@ -7,12 +7,7 @@ "build": {}, "lint": {}, "unit-test": {}, - "int-test": {}, - "code-pushup": {}, - "code-pushup-eslint": {}, - "code-pushup-coverage": {}, - "code-pushup-typescript": {}, - "code-pushup-jsdocs": {} + "int-test": {} }, "tags": ["scope:plugin", "type:feature", "publishable"] } diff --git a/packages/plugin-js-packages/project.json b/packages/plugin-js-packages/project.json index 208cc4668..ffb93e8ca 100644 --- a/packages/plugin-js-packages/project.json +++ b/packages/plugin-js-packages/project.json @@ -6,12 +6,7 @@ "targets": { "build": {}, "lint": {}, - "unit-test": {}, - "code-pushup": {}, - "code-pushup-eslint": {}, - "code-pushup-coverage": {}, - "code-pushup-typescript": {}, - "code-pushup-jsdocs": {} + "unit-test": {} }, "tags": ["scope:plugin", "type:feature", "publishable"], "description": "A plugin for JavaScript packages." diff --git a/packages/plugin-jsdocs/project.json b/packages/plugin-jsdocs/project.json index 4fb8fb531..745540438 100644 --- a/packages/plugin-jsdocs/project.json +++ b/packages/plugin-jsdocs/project.json @@ -8,11 +8,6 @@ "build": {}, "lint": {}, "unit-test": {}, - "int-test": {}, - "code-pushup": {}, - "code-pushup-eslint": {}, - "code-pushup-coverage": {}, - "code-pushup-typescript": {}, - "code-pushup-jsdocs": {} + "int-test": {} } } diff --git a/packages/plugin-lighthouse/project.json b/packages/plugin-lighthouse/project.json index 5ce9c8643..b93152ffb 100644 --- a/packages/plugin-lighthouse/project.json +++ b/packages/plugin-lighthouse/project.json @@ -6,12 +6,7 @@ "targets": { "build": {}, "lint": {}, - "unit-test": {}, - "code-pushup": {}, - "code-pushup-eslint": {}, - "code-pushup-coverage": {}, - "code-pushup-typescript": {}, - "code-pushup-jsdocs": {} + "unit-test": {} }, "tags": ["scope:plugin", "type:feature", "publishable"] } diff --git a/packages/plugin-typescript/project.json b/packages/plugin-typescript/project.json index f6da89dc2..645259554 100644 --- a/packages/plugin-typescript/project.json +++ b/packages/plugin-typescript/project.json @@ -7,12 +7,7 @@ "build": {}, "lint": {}, "unit-test": {}, - "int-test": {}, - "code-pushup": {}, - "code-pushup-eslint": {}, - "code-pushup-coverage": {}, - "code-pushup-typescript": {}, - "code-pushup-jsdocs": {} + "int-test": {} }, "tags": ["scope:plugin", "type:feature", "publishable"] } diff --git a/packages/utils/project.json b/packages/utils/project.json index c8d085a88..54872d001 100644 --- a/packages/utils/project.json +++ b/packages/utils/project.json @@ -20,11 +20,6 @@ }, "unit-test": {}, "int-test": {}, - "code-pushup": {}, - "code-pushup-eslint": {}, - "code-pushup-coverage": {}, - "code-pushup-typescript": {}, - "code-pushup-jsdocs": {}, "demo-logger": { "executor": "nx:run-commands", "options": { diff --git a/project.json b/project.json index 48e03fd94..aaf7de1e1 100644 --- a/project.json +++ b/project.json @@ -1,12 +1,5 @@ { "name": "workspace", "$schema": "node_modules/nx/schemas/project-schema.json", - "targets": { - "code-pushup": { - "dependsOn": [], - "options": { - "args": [] - } - } - } + "targets": {} } diff --git a/tools/scripts/validate-cp-targets.mjs b/tools/scripts/validate-cp-targets.mjs new file mode 100644 index 000000000..680b4296c --- /dev/null +++ b/tools/scripts/validate-cp-targets.mjs @@ -0,0 +1,339 @@ +#!/usr/bin/env node +import { exec } from 'node:child_process'; +import { promises as fs } from 'node:fs'; +import path from 'node:path'; +import { promisify } from 'node:util'; + +const execAsync = promisify(exec); + +const TEMP_GRAPH_FILE = '/tmp/nx-graph-validation.json'; +const TEMP_CONFIG_FILE = '/tmp/cp-config-validation.json'; +const TARGET_NAME = 'code-pushup'; + +function log(message) { + console.log(message); +} + +async function getProjectGraph() { + try { + await execAsync( + `npx nx graph --file=${TEMP_GRAPH_FILE} --open=false 2>/dev/null`, + { timeout: 30_000 }, + ); + + const graphJson = await fs.readFile(TEMP_GRAPH_FILE, 'utf-8'); + const parsed = JSON.parse(graphJson); + return parsed; + } catch (error) { + console.error(`Failed to generate project graph: ${error.message}`); + throw error; + } +} + +async function getProjectConfig(projectRoot) { + const configPath = path.join(projectRoot, 'code-pushup.config.ts'); + try { + await fs.access(configPath); + } catch { + return null; + } + + try { + await execAsync( + `npx @code-pushup/cli print-config --config ${configPath} --output ${TEMP_CONFIG_FILE} 2>/dev/null`, + { timeout: 15000 }, + ); + const configJson = await fs.readFile(TEMP_CONFIG_FILE, 'utf-8'); + return JSON.parse(configJson); + } catch (error) { + return null; + } +} + +async function getProjectJsonTargets(projectRoot) { + const projectJsonPath = path.join(projectRoot, 'project.json'); + const content = await fs.readFile(projectJsonPath, 'utf-8'); + const projectJson = JSON.parse(content); + return projectJson.targets || {}; +} + +function validateProject(projectName, projectNode, config, projectRoot) { + if (!config) { + return { valid: true, issues: [] }; + } + + const plugins = config.plugins || []; + const targetNames = Object.keys(projectNode.data.targets || {}); + const projectJsonTargets = getProjectJsonTargets(projectRoot); + const projectJsonTargetNames = Object.keys(projectJsonTargets); + + const issues = []; + + // Check 1: All plugins have corresponding targets + const missingPlugins = plugins.filter(plugin => { + const targetName = `${TARGET_NAME}-${plugin.slug}`; + return !targetNames.includes(targetName); + }); + + if (missingPlugins.length > 0) { + const pluginNames = missingPlugins + .map(p => `${p.title} (${p.slug})`) + .join(', '); + const configProposal = missingPlugins + .map( + p => `{ + "plugin": "@code-pushup/nx-plugin", + "options": { + "targetName": "${TARGET_NAME}-${p.slug}" + } + }`, + ) + .join(',\n '); + issues.push( + ` ✗ Missing targets for plugins: ${pluginNames} → Add these plugin registrations to nx.json "plugins" array:\n ${configProposal}`, + ); + } + + // Check 2: project.json should not have empty code-pushup targets (they should be inherited from targetDefaults) + const projectJsonCpTargets = projectJsonTargetNames.filter( + t => t.startsWith(TARGET_NAME) && !t.includes('--'), + ); + + const emptyTargets = projectJsonCpTargets.filter(t => { + const target = projectJsonTargets[t]; + return Object.keys(target || {}).length === 0; + }); + + if (emptyTargets.length > 0) { + issues.push( + ` ✗ project.json has empty code-pushup targets: ${emptyTargets.join(', ')} → Remove these empty target definitions (inherit from targetDefaults)`, + ); + } + + return { + valid: issues.length === 0, + issues, + }; +} + +async function getNxJsonPlugins() { + try { + const nxJsonPath = path.join(process.cwd(), 'nx.json'); + const content = await fs.readFile(nxJsonPath, 'utf-8'); + const nxJson = JSON.parse(content); + return (nxJson.plugins || []) + .filter(p => p.plugin === '@code-pushup/nx-plugin') + .map(p => p.options.targetName); + } catch (error) { + console.error(`Failed to read nx.json: ${error.message}`); + process.exit(1); + } +} + +function validateNxJsonPlugins(allConfigPlugins, nxJsonTargets) { + return Array.from(allConfigPlugins).filter( + target => !nxJsonTargets.includes(target), + ); +} + +async function validateProjectNode(projectName, projectNode) { + const config = await getProjectConfig(projectNode.data.root); + const cpTargets = Object.keys(projectNode.data.targets || {}).filter( + t => t.startsWith(TARGET_NAME) && !t.includes('--'), + ); + + if (!config) { + if (cpTargets.length > 0) { + return { + valid: false, + output: `✗ ${projectName} no-config:✓ no-targets:✗(${cpTargets.length})`, + issues: [ + ` ✗ No code-pushup config found but has targets: ${cpTargets.join(', ')}`, + ], + }; + } + return { + valid: true, + output: `✓ ${projectName} no-config:✓ no-targets:✓`, + issues: [], + }; + } + + const pluginCount = (config.plugins || []).length; + const configCount = pluginCount + 1; + const { valid, issues } = validateProject( + projectName, + projectNode, + config, + projectNode.data.root, + ); + + const expectedTargetCount = pluginCount + 1; + const countMatch = cpTargets.length === expectedTargetCount; + const projectJsonTargets = await getProjectJsonTargets(projectNode.data.root); + const projectJsonCpTargets = Object.keys(projectJsonTargets).filter( + t => t.startsWith(TARGET_NAME) && !t.includes('--'), + ); + const emptyTargets = projectJsonCpTargets.filter( + t => Object.keys(projectJsonTargets[t] || {}).length === 0, + ); + const projectJsonStatus = emptyTargets.length === 0 ? '✓' : '✗'; + + const errorMessages = []; + if (issues.length > 0) { + errorMessages.push(...issues); + } + + if (!countMatch) { + const extraTargets = cpTargets.length > expectedTargetCount; + const pluginSlugs = (config.plugins || []).map(p => p.slug); + const extraTargetNames = cpTargets.filter(t => { + if (t === TARGET_NAME) return false; + const slug = t.replace('code-pushup-', ''); + return !pluginSlugs.includes(slug); + }); + + let detailMsg = `config has ${pluginCount} plugins but found ${cpTargets.length} targets (expected ${expectedTargetCount}: 1 main + ${pluginCount} plugins)`; + if (extraTargetNames.length > 0) { + detailMsg += ` (extra: ${extraTargetNames.join(', ')})`; + } + + let fixMsg; + if (extraTargets) { + const extraPlugins = extraTargetNames.map(t => `"${t}"`).join(', '); + fixMsg = `In nx.json, find the plugin registration with targetName: ${extraPlugins} and add "exclude": ["${projectName}"]`; + } else { + fixMsg = `Ensure all plugins in code-pushup.config.ts have corresponding targets in nx.json`; + } + errorMessages.push(` ✗ Target count mismatch: ${detailMsg} → ${fixMsg}`); + } + + const isValid = valid && countMatch && projectJsonStatus === '✓'; + if (isValid) { + return { + valid: true, + output: `✓ ${projectName} config(${configCount}):✓ targets(${cpTargets.length}):✓ projectJson(0):✓`, + issues: [], + }; + } + + const configStatus = valid ? '✓' : '✗'; + const targetStatus = countMatch ? '✓' : '✗'; + return { + valid: false, + output: `✗ ${projectName} config(${configCount}):${configStatus} targets(${cpTargets.length}):${targetStatus} projectJson(${emptyTargets.length}):${projectJsonStatus}`, + issues: errorMessages, + }; +} + +async function validateAllProjects(nodes, shouldCheckProject) { + const results = { + totalValid: 0, + totalInvalid: 0, + invalidProjects: [], + validProjects: [], + allConfigPlugins: new Set(), + }; + + for (const [projectName, projectNode] of Object.entries(nodes)) { + if (!shouldCheckProject(projectName)) { + continue; + } + + const result = await validateProjectNode(projectName, projectNode); + log(result.output); + + if (result.valid) { + results.totalValid++; + results.validProjects.push(projectName); + } else { + results.totalInvalid++; + results.invalidProjects.push({ + name: projectName, + issues: result.issues, + }); + } + + // Collect plugins + const config = await getProjectConfig(projectNode.data.root); + if (config) { + (config.plugins || []).forEach(p => { + results.allConfigPlugins.add(`${TARGET_NAME}-${p.slug}`); + }); + } + } + + return results; +} + +function reportResults(results, nxJsonTargets) { + const missingNxJsonTargets = validateNxJsonPlugins( + results.allConfigPlugins, + nxJsonTargets, + ); + + if (missingNxJsonTargets.length > 0) { + console.log('\n⚠️ Missing plugin registrations in nx.json:'); + missingNxJsonTargets.forEach(target => { + console.log( + ` ✗ ${target} is used in code-pushup configs but not registered in nx.json plugins`, + ); + results.invalidProjects.push({ + name: 'nx.json', + issues: [ + ` ✗ Plugin target "${target}" is missing from nx.json plugins array → Add plugin registration with targetName: "${target}"`, + ], + }); + results.totalInvalid++; + }); + } + + log('\n' + '='.repeat(60)); + log(`Summary: ${results.totalValid} valid, ${results.totalInvalid} invalid`); + log('='.repeat(60)); + + if (results.invalidProjects.length > 0) { + log('\n❌ Issues found:\n'); + results.invalidProjects.forEach(({ name, issues }) => { + log(`${name}:`); + issues.forEach(issue => log(issue)); + log(''); + }); + process.exit(1); + } else { + log('\n✅ All projects have valid Code PushUp target configurations!'); + process.exit(0); + } +} + +/** + * Main entry point for validating Code PushUp targets against the Nx project graph. + * + * Validates that: + * 1. All plugins in code-pushup configs have corresponding targets in the Nx graph + * 2. All plugins used in configs are registered in nx.json + * 3. project.json files don't have empty code-pushup target definitions + * + * Supports filtering by project names via command line arguments: + * @example + * node validate-cp-targets.mjs // Check all projects + * node validate-cp-targets.mjs models utils // Check only models and utils + */ +export async function main() { + log('🔍 Validating Code PushUp targets against project graph...\n'); + + const graph = await getProjectGraph(); + const nodes = graph.graph.nodes; + const nxJsonTargets = await getNxJsonPlugins(); + + const projectFilter = process.argv.slice(2); + const shouldCheckProject = + projectFilter.length === 0 + ? () => true + : name => projectFilter.includes(name); + + const results = await validateAllProjects(nodes, shouldCheckProject); + reportResults(results, nxJsonTargets); +} + +main();