diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4c87bcf90..3deef2278 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,6 +83,21 @@ Therefore, PRs are merged via one of two strategies: - rebase - branch cannot contain merge commits ([rebase instead of merge](https://www.atlassian.com/git/tutorials/merging-vs-rebasing)), - squash - single commit whose message is the PR title (should be in conventional commit format). +## Releases + +We use nx release command to create releases for GitHub as well as publish to npm. + +**Preconditions:** + +- `npm login` - Only users with write access to [code-pushup](https://www.npmjs.com/org/code-pushup) can publish +- (optional) `GITHUB_TOKEN=ghp_...` in `.env` - [Personal access token](https://github.com/settings/personal-access-tokens/new) to create a GitHub release. + +**Steps:** + +- `git checkout main`, `git pull` +- (recommended optional) `npx nx release --dryRun` +- `npx nx release` and confirm publish prompt + ## Project tags [Nx tags](https://nx.dev/core-features/enforce-module-boundaries) are used to enforce module boundaries in the project graph when linting. diff --git a/code-pushup.config.ts b/code-pushup.config.ts index 4ea96774f..0f0f669fa 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -20,6 +20,9 @@ const config: CoreConfig = { server: 'https://api.staging.code-pushup.dev/graphql', apiKey: process.env['CP_API_KEY'], }, + persist: { + outputDir: '.code-pushup', + }, }), plugins: [], }; 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 fb5408812..7a446969c 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 @@ -3,6 +3,7 @@ import path from 'node:path'; import { readProjectConfiguration } from 'nx/src/generators/utils/project-configuration'; import { afterEach, expect } from 'vitest'; import { generateCodePushupConfig } from '@code-pushup/nx-plugin'; +import { PACKAGE_NAME } from '@code-pushup/nx-plugin/src/internal/constants.js'; import { generateWorkspaceAndProject, materializeTree, @@ -120,7 +121,7 @@ describe('nx-plugin', () => { expect(projectJson.targets).toStrictEqual({ 'code-pushup--configuration': expect.objectContaining({ options: { - command: `nx g XYZ:configuration --project="${project}"`, + command: `nx g ${PACKAGE_NAME}:configuration --project="${project}"`, }, }), }); diff --git a/nx.json b/nx.json index 7b41eba43..4a023bf26 100644 --- a/nx.json +++ b/nx.json @@ -140,7 +140,7 @@ "watch": false } }, - "code-pushup": { + "old-code-pushup": { "cache": false, "outputs": [ "{projectRoot}/.code-pushup/report.md", @@ -347,6 +347,16 @@ "releaseTagPattern": "v{version}" }, "plugins": [ + { + "plugin": "@code-pushup/nx-plugin", + "options": { + "bin": "packages/cli/src/index.ts", + "env": { + "NODE_OPTIONS": "--import tsx", + "TSX_TSCONFIG_PATH": "tsconfig.base.json" + } + } + }, { "plugin": "@push-based/nx-verdaccio", "options": { diff --git a/package-lock.json b/package-lock.json index b8d79a01d..ef0c9a261 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.0.0", "license": "MIT", "dependencies": { - "@code-pushup/portal-client": "^0.16.0", "@isaacs/cliui": "^8.0.2", "@nx/devkit": "21.4.1", "@poppinss/cliui": "6.4.1", @@ -40,7 +39,10 @@ "@actions/core": "^1.11.1", "@actions/github": "^6.0.1", "@beaussan/nx-knip": "^0.0.5-15", + "@code-pushup/cli": "https://pkg.pr.new/code-pushup/cli/@code-pushup/cli@1091", "@code-pushup/eslint-config": "^0.14.2", + "@code-pushup/nx-plugin": "https://pkg.pr.new/code-pushup/cli/@code-pushup/nx-plugin@1091", + "@code-pushup/portal-client": "^0.16.0", "@commitlint/cli": "^19.5.0", "@commitlint/config-conventional": "^19.5.0", "@commitlint/config-nx-scopes": "^19.5.0", @@ -2315,6 +2317,47 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/@code-pushup/cli": { + "version": "0.85.0", + "resolved": "https://pkg.pr.new/code-pushup/cli/@code-pushup/cli@1091", + "integrity": "sha512-fY/JOxRXCY6qL2n1Kfos0AV+coRRALAZjxisfstquqSb74F0Obh02+dHHnhxR7AaQi39aM2WdpRhc5YFD3lOzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@code-pushup/core": "https://pkg.pr.new/code-pushup/cli/@code-pushup/core@3fb67471d20437e38cc29263e3c6d9e13a4a8f75", + "@code-pushup/models": "https://pkg.pr.new/code-pushup/cli/@code-pushup/models@3fb67471d20437e38cc29263e3c6d9e13a4a8f75", + "@code-pushup/utils": "https://pkg.pr.new/code-pushup/cli/@code-pushup/utils@3fb67471d20437e38cc29263e3c6d9e13a4a8f75", + "ansis": "^3.3.0", + "simple-git": "^3.20.0", + "yargs": "^17.7.2" + }, + "bin": { + "code-pushup": "src/index.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@code-pushup/core": { + "version": "0.85.0", + "resolved": "https://pkg.pr.new/code-pushup/cli/@code-pushup/core@3fb67471d20437e38cc29263e3c6d9e13a4a8f75", + "integrity": "sha512-oWpUtPbG+ovzytIacsld9RhTt7gJfuEpDUxV24V7g/iR7GSUvlYNciFQkSlm/OGxkDrY9VztygnwjSfUDSLj8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@code-pushup/models": "https://pkg.pr.new/code-pushup/cli/@code-pushup/models@3fb67471d20437e38cc29263e3c6d9e13a4a8f75", + "@code-pushup/utils": "https://pkg.pr.new/code-pushup/cli/@code-pushup/utils@3fb67471d20437e38cc29263e3c6d9e13a4a8f75", + "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", @@ -2399,10 +2442,36 @@ } } }, + "node_modules/@code-pushup/models": { + "version": "0.85.0", + "resolved": "https://pkg.pr.new/code-pushup/cli/@code-pushup/models@3fb67471d20437e38cc29263e3c6d9e13a4a8f75", + "integrity": "sha512-DAkXNfuL6O0ymZ2OZ9NyR5rN0Vgrw3i7tSCKL0ncDDVMNjh8S0FFuJFlPZyjFEZqOVbTVFJ4lVOozPFS21OP1A==", + "dev": true, + "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.85.0", + "resolved": "https://pkg.pr.new/code-pushup/cli/@code-pushup/nx-plugin@1091", + "integrity": "sha512-LUdaqKsfF4IACGOvdC1/57fxbYFMPjEMNH73cM2TRMIpHcO+/WwxqcZeBoMGzVBVLft4U4HEntIpHUVGeuxOYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@code-pushup/models": "https://pkg.pr.new/code-pushup/cli/@code-pushup/models@3fb67471d20437e38cc29263e3c6d9e13a4a8f75", + "@code-pushup/utils": "https://pkg.pr.new/code-pushup/cli/@code-pushup/utils@3fb67471d20437e38cc29263e3c6d9e13a4a8f75", + "@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", "integrity": "sha512-JlMRcTKkJygVfLS+IWQxDRDnvF64p4q+QDLIXzQPep6X99C1OH3MnA9jbfGAOew/3xqOILCrifn0y54fyRs8Qg==", + "dev": true, "license": "MIT", "dependencies": { "graphql": "^16.6.0", @@ -2411,6 +2480,30 @@ "vscode-material-icons": "^0.1.0" } }, + "node_modules/@code-pushup/utils": { + "version": "0.85.0", + "resolved": "https://pkg.pr.new/code-pushup/cli/@code-pushup/utils@3fb67471d20437e38cc29263e3c6d9e13a4a8f75", + "integrity": "sha512-eyvHJftWoLntmh4PptZxPztvYaePFoiAImBiL7nQhLVZSL5P7qyCM8KfVLsYh7hlNmOCN/l/ThavhYWuB0QGfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@code-pushup/models": "https://pkg.pr.new/code-pushup/cli/@code-pushup/models@3fb67471d20437e38cc29263e3c6d9e13a4a8f75", + "@isaacs/cliui": "^8.0.2", + "@poppinss/cliui": "^6.4.0", + "ansis": "^3.3.0", + "build-md": "^0.4.2", + "bundle-require": "^5.1.0", + "esbuild": "^0.25.2", + "multi-progress-bars": "^5.0.3", + "ora": "^9.0.0", + "semver": "^7.6.0", + "simple-git": "^3.20.0", + "zod": "^4.0.5" + }, + "engines": { + "node": ">=17.0.0" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -3355,6 +3448,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "dev": true, "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } @@ -14182,6 +14276,7 @@ "version": "3.1.8", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dev": true, "dependencies": { "node-fetch": "^2.6.12" } @@ -15164,7 +15259,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "iconv-lite": "^0.6.2" @@ -15174,7 +15269,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -18235,6 +18330,7 @@ "version": "16.9.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "dev": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -18243,6 +18339,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-6.1.0.tgz", "integrity": "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==", + "dev": true, "dependencies": { "@graphql-typed-document-node/core": "^3.2.0", "cross-fetch": "^3.1.5" @@ -18255,6 +18352,7 @@ "version": "2.12.6", "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", + "dev": true, "dependencies": { "tslib": "^2.1.0" }, @@ -23540,6 +23638,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -23558,17 +23657,20 @@ "node_modules/node-fetch/node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true }, "node_modules/node-fetch/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true }, "node_modules/node-fetch/node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -27216,7 +27318,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true + "dev": true }, "node_modules/saxes": { "version": "6.0.0", diff --git a/package.json b/package.json index a5fec600c..ba15ffa73 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ }, "private": true, "dependencies": { - "@code-pushup/portal-client": "^0.16.0", "@isaacs/cliui": "^8.0.2", "@nx/devkit": "21.4.1", "@poppinss/cliui": "6.4.1", @@ -50,7 +49,10 @@ "@actions/core": "^1.11.1", "@actions/github": "^6.0.1", "@beaussan/nx-knip": "^0.0.5-15", + "@code-pushup/cli": "https://pkg.pr.new/code-pushup/cli/@code-pushup/cli@1091", "@code-pushup/eslint-config": "^0.14.2", + "@code-pushup/nx-plugin": "https://pkg.pr.new/code-pushup/cli/@code-pushup/nx-plugin@1091", + "@code-pushup/portal-client": "^0.16.0", "@commitlint/cli": "^19.5.0", "@commitlint/config-conventional": "^19.5.0", "@commitlint/config-nx-scopes": "^19.5.0", diff --git a/packages/nx-plugin/src/executors/cli/executor.ts b/packages/nx-plugin/src/executors/cli/executor.ts index 2e644f184..b173dc5cc 100644 --- a/packages/nx-plugin/src/executors/cli/executor.ts +++ b/packages/nx-plugin/src/executors/cli/executor.ts @@ -27,11 +27,12 @@ export default async function runAutorunExecutor( mergedOptions, normalizedContext, ); - const { dryRun, verbose, command, bin } = mergedOptions; + const { dryRun, verbose, command, bin, env } = mergedOptions; const commandString = createCliCommandString({ command, args: cliArgumentObject, bin, + env, }); if (verbose) { logger.info(`Run CLI executor ${command ?? ''}`); @@ -42,7 +43,12 @@ export default async function runAutorunExecutor( } else { try { await executeProcess({ - ...createCliCommandObject({ command, args: cliArgumentObject, bin }), + ...createCliCommandObject({ + command, + args: cliArgumentObject, + bin, + env, + }), ...(context.cwd ? { cwd: context.cwd } : {}), }); } catch (error) { diff --git a/packages/nx-plugin/src/executors/cli/schema.json b/packages/nx-plugin/src/executors/cli/schema.json index 85cd0de19..635c43c66 100644 --- a/packages/nx-plugin/src/executors/cli/schema.json +++ b/packages/nx-plugin/src/executors/cli/schema.json @@ -21,6 +21,13 @@ "type": "string", "description": "Path to Code PushUp CLI" }, + "env": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Environment variables to pass to the CLI command" + }, "verbose": { "type": "boolean", "description": "Print additional logs" diff --git a/packages/nx-plugin/src/executors/cli/utils.ts b/packages/nx-plugin/src/executors/cli/utils.ts index 5530e00c0..5641b07dd 100644 --- a/packages/nx-plugin/src/executors/cli/utils.ts +++ b/packages/nx-plugin/src/executors/cli/utils.ts @@ -34,7 +34,7 @@ export function parseAutorunExecutorOptions( options: Partial, normalizedContext: NormalizedExecutorContext, ): AutorunCommandExecutorOptions { - const { projectPrefix, persist, upload, command } = options; + const { projectPrefix, persist, upload, command, output } = options; const needsUploadParams = command === 'upload' || command === 'autorun' || command === undefined; const uploadCfg = uploadConfig( @@ -46,6 +46,7 @@ export function parseAutorunExecutorOptions( ...parsePrintConfigExecutorOptions(options), ...parseAutorunExecutorOnlyOptions(options), ...globalConfig(options, normalizedContext), + ...(output ? { output } : {}), persist: persistConfig({ projectPrefix, ...persist }, normalizedContext), // @TODO This is a hack to avoid validation errors of upload config for commands that dont need it. // Fix: use utils and execute the core logic directly diff --git a/packages/nx-plugin/src/executors/cli/utils.unit.test.ts b/packages/nx-plugin/src/executors/cli/utils.unit.test.ts index 025c322fb..647640215 100644 --- a/packages/nx-plugin/src/executors/cli/utils.unit.test.ts +++ b/packages/nx-plugin/src/executors/cli/utils.unit.test.ts @@ -170,6 +170,33 @@ describe('parseAutorunExecutorOptions', () => { ); }, ); + + it.each(['print-config'])( + 'should include output config for command %s', + command => { + const projectName = 'my-app'; + const executorOptions = parseAutorunExecutorOptions( + { + command, + output: 'code-pushup.config.json', + }, + { + projectName, + workspaceRoot: 'workspaceRoot', + projectConfig: { + name: 'my-app', + root: 'root', + }, + }, + ); + + expect(executorOptions).toEqual( + expect.objectContaining({ + output: 'code-pushup.config.json', + }), + ); + }, + ); }); describe('mergeExecutorOptions', () => { diff --git a/packages/nx-plugin/src/executors/internal/cli.ts b/packages/nx-plugin/src/executors/internal/cli.ts index adbf1627c..bc2a134bf 100644 --- a/packages/nx-plugin/src/executors/internal/cli.ts +++ b/packages/nx-plugin/src/executors/internal/cli.ts @@ -1,25 +1,49 @@ -import { logger } from '@nx/devkit'; - export function createCliCommandString(options?: { args?: Record; command?: string; bin?: string; + env?: Record; }): string { - const { bin = '@code-pushup/cli', command, args } = options ?? {}; - return `npx ${bin} ${objectToCliArgs({ _: command ?? [], ...args }).join( - ' ', - )}`; + const { bin = '@code-pushup/cli', command, args, env } = options ?? {}; + const isFile = isFilePath(bin); + const envTerminalString = Object.entries(env ?? {}) + .map(([key, value]) => `${key}=${value}`) + .join(' '); + const commandPrefix = isFile ? 'node' : 'npx'; + const envPrefix = envTerminalString ? `${envTerminalString} ` : ''; + return `${envPrefix}${commandPrefix} ${bin} ${objectToCliArgs({ + _: command ?? [], + ...args, + }).join(' ')}`; +} + +function isFilePath(bin: string): boolean { + return ( + /\.(js|ts|mjs|cjs)$/.test(bin) || + bin.startsWith('./') || + bin.startsWith('../') || + (bin.includes('/') && !bin.startsWith('@')) + ); } export function createCliCommandObject(options?: { args?: Record; command?: string; bin?: string; + env?: Record; }): import('@code-pushup/utils').ProcessConfig { - const { bin = '@code-pushup/cli', command, args } = options ?? {}; + const { bin = '@code-pushup/cli', command, args, env } = options ?? {}; + const isFile = isFilePath(bin); + const envTerminalString = Object.entries(env ?? {}) + .map(([key, value]) => `${key}=${value}`) + .join(' '); return { - command: 'npx', - args: [bin, ...objectToCliArgs({ _: command ?? [], ...args })], + command: isFile ? 'node' : 'npx', + args: [ + ...(envTerminalString ? [envTerminalString] : []), + bin, + ...objectToCliArgs({ _: command ?? [], ...args }), + ], }; } diff --git a/packages/nx-plugin/src/executors/internal/cli.unit.test.ts b/packages/nx-plugin/src/executors/internal/cli.unit.test.ts index 279f23505..36f79361f 100644 --- a/packages/nx-plugin/src/executors/internal/cli.unit.test.ts +++ b/packages/nx-plugin/src/executors/internal/cli.unit.test.ts @@ -102,6 +102,27 @@ describe('createCliCommandString', () => { }); expect(result).toBe('npx @code-pushup/cli autorun --verbose'); }); + + it('should use node for file paths', () => { + const result = createCliCommandString({ + bin: 'packages/cli/src/index.ts', + args: { verbose: true }, + }); + expect(result).toBe('node packages/cli/src/index.ts --verbose'); + }); + + it('should include env variables in command string', () => { + const result = createCliCommandString({ + args: { verbose: true }, + env: { + NODE_OPTIONS: '--import tsx', + TSX_TSCONFIG_PATH: 'tsconfig.base.json', + }, + }); + expect(result).toBe( + 'NODE_OPTIONS=--import tsx TSX_TSCONFIG_PATH=tsconfig.base.json npx @code-pushup/cli --verbose', + ); + }); }); describe('createCliCommandObject', () => { @@ -130,6 +151,18 @@ describe('createCliCommandObject', () => { }), ).toStrictEqual({ args: ['node_modules/@code-pushup/cli/src/bin.js'], + command: 'node', + }); + }); + + it('should create command out of object for arguments with env', () => { + expect( + createCliCommandObject({ + args: { verbose: true }, + env: { NODE_ENV: 'production', DEBUG: 'true' }, + }), + ).toStrictEqual({ + args: ['NODE_ENV=production DEBUG=true', '@code-pushup/cli', '--verbose'], command: 'npx', }); }); diff --git a/packages/nx-plugin/src/executors/internal/types.ts b/packages/nx-plugin/src/executors/internal/types.ts index 2f529038a..677d472ce 100644 --- a/packages/nx-plugin/src/executors/internal/types.ts +++ b/packages/nx-plugin/src/executors/internal/types.ts @@ -30,6 +30,7 @@ export type Command = export type GlobalExecutorOptions = { command?: Command; bin?: string; + env?: Record; verbose?: boolean; progress?: boolean; config?: string; diff --git a/packages/nx-plugin/src/generators/configuration/generator.int.test.ts b/packages/nx-plugin/src/generators/configuration/generator.int.test.ts index 25c6cbae2..ba779c955 100644 --- a/packages/nx-plugin/src/generators/configuration/generator.int.test.ts +++ b/packages/nx-plugin/src/generators/configuration/generator.int.test.ts @@ -7,7 +7,6 @@ import { import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import * as path from 'node:path'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import { DEFAULT_TARGET_NAME, PACKAGE_NAME } from '../../internal/constants.js'; import { configurationGenerator } from './generator.js'; describe('configurationGenerator', () => { diff --git a/packages/nx-plugin/src/generators/configuration/schema.d.ts b/packages/nx-plugin/src/generators/configuration/schema.d.ts index b105270c6..0f0cb7b79 100644 --- a/packages/nx-plugin/src/generators/configuration/schema.d.ts +++ b/packages/nx-plugin/src/generators/configuration/schema.d.ts @@ -2,7 +2,6 @@ import type { DynamicTargetOptions } from '../../internal/types.js'; export type ConfigurationGeneratorOptions = { project: string; - bin?: string; skipTarget?: boolean; skipConfig?: boolean; skipFormat?: boolean; diff --git a/packages/nx-plugin/src/index.ts b/packages/nx-plugin/src/index.ts index c074211c6..6c6f4b08f 100644 --- a/packages/nx-plugin/src/index.ts +++ b/packages/nx-plugin/src/index.ts @@ -1,15 +1,6 @@ -import { createNodes, createNodesV2 } from './plugin/index.js'; - -// default export for nx.json#plugins -const plugin = { - name: '@code-pushup/nx-plugin', - createNodesV2, - // Keep for backwards compatibility with Nx < 21 - createNodes, -}; - -export default plugin; +import { plugin } from './plugin/index.js'; +export { createNodes, createNodesV2 } from './plugin/index.js'; export type { AutorunCommandExecutorOptions } from './executors/cli/schema.js'; export { objectToCliArgs } from './executors/internal/cli.js'; export { generateCodePushupConfig } from './generators/configuration/code-pushup-config.js'; @@ -18,4 +9,5 @@ export type { ConfigurationGeneratorOptions } from './generators/configuration/s export { initGenerator, initSchematic } from './generators/init/generator.js'; export { type InitGeneratorSchema } from './generators/init/schema.js'; export * from './internal/versions.js'; -export { createNodes, createNodesV2 } from './plugin/index.js'; + +export default plugin; diff --git a/packages/nx-plugin/src/internal/types.ts b/packages/nx-plugin/src/internal/types.ts index bf3a2d047..3a34d86d7 100644 --- a/packages/nx-plugin/src/internal/types.ts +++ b/packages/nx-plugin/src/internal/types.ts @@ -1,4 +1,5 @@ export type DynamicTargetOptions = { targetName?: string; bin?: string; + env?: Record; }; diff --git a/packages/nx-plugin/src/plugin/index.ts b/packages/nx-plugin/src/plugin/index.ts index 6af7c1076..450cf2ced 100644 --- a/packages/nx-plugin/src/plugin/index.ts +++ b/packages/nx-plugin/src/plugin/index.ts @@ -1,2 +1,2 @@ -export { createNodes, createNodesV2 } from './plugin.js'; +export { createNodes, createNodesV2, plugin } from './plugin.js'; export type { CreateNodesOptions } from './types.js'; diff --git a/packages/nx-plugin/src/plugin/plugin.ts b/packages/nx-plugin/src/plugin/plugin.ts index 1b4e3e2f1..37a178619 100644 --- a/packages/nx-plugin/src/plugin/plugin.ts +++ b/packages/nx-plugin/src/plugin/plugin.ts @@ -5,6 +5,7 @@ import type { CreateNodesResult, CreateNodesResultV2, CreateNodesV2, + NxPlugin, } from '@nx/devkit'; import { PROJECT_JSON_FILE_NAME } from '../internal/constants.js'; import { createTargets } from './target/targets.js'; @@ -39,14 +40,15 @@ export const createNodes: CreateNodes = [ }, ]; -export const createNodesV2: CreateNodesV2 = [ +export const createNodesV2: CreateNodesV2 = [ `**/${PROJECT_JSON_FILE_NAME}`, async ( projectConfigurationFiles: readonly string[], createNodesOptions: unknown, context: CreateNodesContextV2, ): Promise => { - const parsedCreateNodesOptions = createNodesOptions as CreateNodesOptions; + const parsedCreateNodesOptions = + (createNodesOptions as CreateNodesOptions) ?? {}; return await Promise.all( projectConfigurationFiles.map(async projectConfigurationFile => { @@ -69,3 +71,9 @@ export const createNodesV2: CreateNodesV2 = [ ); }, ]; + +export const plugin = { + name: 'code-pushup', + createNodesV2: createNodesV2 as CreateNodesV2, + createNodes, +} satisfies NxPlugin; diff --git a/packages/nx-plugin/src/plugin/target/configuration-target.ts b/packages/nx-plugin/src/plugin/target/configuration-target.ts index cc9655969..986d3ea4e 100644 --- a/packages/nx-plugin/src/plugin/target/configuration-target.ts +++ b/packages/nx-plugin/src/plugin/target/configuration-target.ts @@ -5,13 +5,12 @@ import { PACKAGE_NAME } from '../../internal/constants.js'; export function createConfigurationTarget(options?: { projectName?: string; - bin?: string; }): TargetConfiguration { - const { projectName, bin = PACKAGE_NAME } = options ?? {}; + const { projectName } = options ?? {}; const args = objectToCliArgs({ ...(projectName ? { project: projectName } : {}), }); return { - command: `nx g ${bin}:configuration${args.length > 0 ? ` ${args.join(' ')}` : ''}`, + command: `nx g ${PACKAGE_NAME}:configuration${args.length > 0 ? ` ${args.join(' ')}` : ''}`, }; } diff --git a/packages/nx-plugin/src/plugin/target/executor-target.ts b/packages/nx-plugin/src/plugin/target/executor-target.ts index d8cfab569..df968154b 100644 --- a/packages/nx-plugin/src/plugin/target/executor-target.ts +++ b/packages/nx-plugin/src/plugin/target/executor-target.ts @@ -5,13 +5,17 @@ import type { ProjectPrefixOptions } from '../types.js'; export function createExecutorTarget(options?: { bin?: string; projectPrefix?: string; -}): TargetConfiguration { - const { bin, projectPrefix } = options ?? {}; + env?: Record; +}): TargetConfiguration< + ProjectPrefixOptions & { env?: Record } +> { + const { bin, projectPrefix, env } = options ?? {}; const executor = `${PACKAGE_NAME}:cli`; - const executorOptions = (bin || projectPrefix) && { + const executorOptions = (bin || projectPrefix || env) && { ...(bin && { bin }), ...(projectPrefix && { projectPrefix }), + ...(env && { env }), }; return { executor, ...(executorOptions && { options: executorOptions }) }; } diff --git a/packages/nx-plugin/src/plugin/target/executor.target.unit.test.ts b/packages/nx-plugin/src/plugin/target/executor.target.unit.test.ts index 2926d3367..baec99a6d 100644 --- a/packages/nx-plugin/src/plugin/target/executor.target.unit.test.ts +++ b/packages/nx-plugin/src/plugin/target/executor.target.unit.test.ts @@ -27,4 +27,23 @@ describe('createExecutorTarget', () => { }, }); }); + + it('should use env if provided', () => { + expect( + createExecutorTarget({ + env: { + NODE_OPTIONS: '--import tsx', + TSX_TSCONFIG_PATH: 'tsconfig.base.json', + }, + }), + ).toStrictEqual({ + executor: '@code-pushup/nx-plugin:cli', + options: { + env: { + NODE_OPTIONS: '--import tsx', + TSX_TSCONFIG_PATH: 'tsconfig.base.json', + }, + }, + }); + }); }); diff --git a/packages/nx-plugin/src/plugin/target/targets.ts b/packages/nx-plugin/src/plugin/target/targets.ts index ae192ffd8..90abaa047 100644 --- a/packages/nx-plugin/src/plugin/target/targets.ts +++ b/packages/nx-plugin/src/plugin/target/targets.ts @@ -19,17 +19,17 @@ export async function createTargets(normalizedContext: CreateTargetsOptions) { targetName = CP_TARGET_NAME, bin, projectPrefix, + env, } = normalizedContext.createOptions; const rootFiles = await readdir(normalizedContext.projectRoot); return rootFiles.some(filename => filename.match(CODE_PUSHUP_CONFIG_REGEX)) ? { - [targetName]: createExecutorTarget({ bin, projectPrefix }), + [targetName]: createExecutorTarget({ bin, projectPrefix, env }), } : // if NO code-pushup.config.*.(ts|js|mjs) is present return configuration target { [`${targetName}--configuration`]: createConfigurationTarget({ projectName: normalizedContext.projectJson.name, - bin, }), }; } diff --git a/project.json b/project.json index c53ed1a80..7e5be7514 100644 --- a/project.json +++ b/project.json @@ -25,19 +25,6 @@ ] }, "code-pushup-jsdocs": {}, - "code-pushup-typescript": {}, - "code-pushup": { - "dependsOn": ["code-pushup-*"], - "executor": "nx:run-commands", - "options": { - "args": [ - "--no-progress", - "--verbose", - "--cache.read", - "--persist.outputDir={projectRoot}/.code-pushup", - "--upload.project={projectName}" - ] - } - } + "code-pushup-typescript": {} } }