diff --git a/packages/nx/src/plugins/package-json/create-nodes.ts b/packages/nx/src/plugins/package-json/create-nodes.ts index e983c8f7eaa3d..0670dffcd7b13 100644 --- a/packages/nx/src/plugins/package-json/create-nodes.ts +++ b/packages/nx/src/plugins/package-json/create-nodes.ts @@ -177,7 +177,7 @@ export function buildProjectConfigurationFromPackageJson( sourceRoot: projectRoot, name, ...packageJson.nx, - targets: readTargetsFromPackageJson(packageJson), + targets: readTargetsFromPackageJson(packageJson, nxJson), tags: getTagsFromPackageJson(packageJson), metadata: getMetadataFromPackageJson(packageJson), }; diff --git a/packages/nx/src/utils/package-json.spec.ts b/packages/nx/src/utils/package-json.spec.ts index f5b00a0b7f1d5..1e2967fe95062 100644 --- a/packages/nx/src/utils/package-json.spec.ts +++ b/packages/nx/src/utils/package-json.spec.ts @@ -40,8 +40,50 @@ describe('readTargetsFromPackageJson', () => { }, }; + it('should take targetDefaults for nx-release-publish into account when building the implicit target', () => { + const nxJson1 = { + targetDefaults: { + 'nx-release-publish': { + dependsOn: ['build', 'lint'], + }, + }, + }; + const result1 = readTargetsFromPackageJson(packageJson, nxJson1); + expect(result1['nx-release-publish']).toMatchInlineSnapshot(` + { + "dependsOn": [ + "^nx-release-publish", + "build", + "lint", + ], + "executor": "@nx/js:release-publish", + "options": {}, + } + `); + + const nxJson2 = { + targetDefaults: { + 'nx-release-publish': { + dependsOn: ['^something'], + executor: 'totally-different-executor', + }, + }, + }; + const result2 = readTargetsFromPackageJson(packageJson, nxJson2); + expect(result2['nx-release-publish']).toMatchInlineSnapshot(` + { + "dependsOn": [ + "^nx-release-publish", + "^something", + ], + "executor": "totally-different-executor", + "options": {}, + } + `); + }); + it('should read targets from project.json and package.json', () => { - const result = readTargetsFromPackageJson(packageJson); + const result = readTargetsFromPackageJson(packageJson, {}); expect(result).toMatchInlineSnapshot(` { "build": { @@ -66,20 +108,23 @@ describe('readTargetsFromPackageJson', () => { }); it('should contain extended options from nx property in package.json', () => { - const result = readTargetsFromPackageJson({ - name: 'my-other-app', - version: '', - scripts: { - build: 'echo 1', - }, - nx: { - targets: { - build: { - outputs: ['custom'], + const result = readTargetsFromPackageJson( + { + name: 'my-other-app', + version: '', + scripts: { + build: 'echo 1', + }, + nx: { + targets: { + build: { + outputs: ['custom'], + }, }, }, }, - }); + {} + ); expect(result).toEqual({ build: { ...packageJsonBuildTarget, outputs: ['custom'] }, 'nx-release-publish': { @@ -91,17 +136,20 @@ describe('readTargetsFromPackageJson', () => { }); it('should ignore scripts that are not in includedScripts', () => { - const result = readTargetsFromPackageJson({ - name: 'included-scripts-test', - version: '', - scripts: { - test: 'echo testing', - fail: 'exit 1', - }, - nx: { - includedScripts: ['test'], + const result = readTargetsFromPackageJson( + { + name: 'included-scripts-test', + version: '', + scripts: { + test: 'echo testing', + fail: 'exit 1', + }, + nx: { + includedScripts: ['test'], + }, }, - }); + {} + ); expect(result).toMatchInlineSnapshot(` { @@ -127,20 +175,23 @@ describe('readTargetsFromPackageJson', () => { }); it('should extend script based targets if matching config', () => { - const result = readTargetsFromPackageJson({ - name: 'my-other-app', - version: '', - scripts: { - build: 'echo 1', - }, - nx: { - targets: { - build: { - outputs: ['custom'], + const result = readTargetsFromPackageJson( + { + name: 'my-other-app', + version: '', + scripts: { + build: 'echo 1', + }, + nx: { + targets: { + build: { + outputs: ['custom'], + }, }, }, }, - }); + {} + ); expect(result.build).toMatchInlineSnapshot(` { "executor": "nx:run-script", @@ -159,23 +210,26 @@ describe('readTargetsFromPackageJson', () => { }); it('should override scripts if provided an executor', () => { - const result = readTargetsFromPackageJson({ - name: 'my-other-app', - version: '', - scripts: { - build: 'echo 1', - }, - nx: { - targets: { - build: { - executor: 'nx:run-commands', - options: { - commands: ['echo 2'], + const result = readTargetsFromPackageJson( + { + name: 'my-other-app', + version: '', + scripts: { + build: 'echo 1', + }, + nx: { + targets: { + build: { + executor: 'nx:run-commands', + options: { + commands: ['echo 2'], + }, }, }, }, }, - }); + {} + ); expect(result.build).toMatchInlineSnapshot(` { "executor": "nx:run-commands", @@ -189,23 +243,26 @@ describe('readTargetsFromPackageJson', () => { }); it('should override script if provided in options', () => { - const result = readTargetsFromPackageJson({ - name: 'my-other-app', - version: '', - scripts: { - build: 'echo 1', - }, - nx: { - targets: { - build: { - executor: 'nx:run-script', - options: { - script: 'echo 2', + const result = readTargetsFromPackageJson( + { + name: 'my-other-app', + version: '', + scripts: { + build: 'echo 1', + }, + nx: { + targets: { + build: { + executor: 'nx:run-script', + options: { + script: 'echo 2', + }, }, }, }, }, - }); + {} + ); expect(result.build).toMatchInlineSnapshot(` { "executor": "nx:run-script", @@ -217,20 +274,23 @@ describe('readTargetsFromPackageJson', () => { }); it('should support targets without scripts', () => { - const result = readTargetsFromPackageJson({ - name: 'my-other-app', - version: '', - nx: { - targets: { - build: { - executor: 'nx:run-commands', - options: { - commands: ['echo 2'], + const result = readTargetsFromPackageJson( + { + name: 'my-other-app', + version: '', + nx: { + targets: { + build: { + executor: 'nx:run-commands', + options: { + commands: ['echo 2'], + }, }, }, }, }, - }); + {} + ); expect(result.build).toMatchInlineSnapshot(` { "executor": "nx:run-commands", @@ -244,57 +304,60 @@ describe('readTargetsFromPackageJson', () => { }); it('should support partial target info without including script', () => { - const result = readTargetsFromPackageJson({ - name: 'my-remix-app-8cce', - version: '', - scripts: { - build: 'run-s build:*', - 'build:icons': 'tsx ./other/build-icons.ts', - 'build:remix': 'remix build --sourcemap', - 'build:server': 'tsx ./other/build-server.ts', - predev: 'npm run build:icons --silent', - dev: 'remix dev -c "node ./server/dev-server.js" --manual', - 'prisma:studio': 'prisma studio', - format: 'prettier --write .', - lint: 'eslint .', - setup: - 'npm run build && prisma generate && prisma migrate deploy && prisma db seed && playwright install', - start: 'cross-env NODE_ENV=production node .', - 'start:mocks': 'cross-env NODE_ENV=production MOCKS=true tsx .', - test: 'vitest', - coverage: 'nx test --coverage', - 'test:e2e': 'npm run test:e2e:dev --silent', - 'test:e2e:dev': 'playwright test --ui', - 'pretest:e2e:run': 'npm run build', - 'test:e2e:run': 'cross-env CI=true playwright test', - 'test:e2e:install': 'npx playwright install --with-deps chromium', - typecheck: 'tsc', - validate: 'run-p "test -- --run" lint typecheck test:e2e:run', - }, - nx: { - targets: { - 'build:icons': { - outputs: ['{projectRoot}/app/components/ui/icons'], - }, - 'build:remix': { - outputs: ['{projectRoot}/build'], - }, - 'build:server': { - outputs: ['{projectRoot}/server-build'], - }, - test: { - outputs: ['{projectRoot}/test-results'], - }, - 'test:e2e': { - outputs: ['{projectRoot}/playwright-report'], - }, - 'test:e2e:run': { - outputs: ['{projectRoot}/playwright-report'], + const result = readTargetsFromPackageJson( + { + name: 'my-remix-app-8cce', + version: '', + scripts: { + build: 'run-s build:*', + 'build:icons': 'tsx ./other/build-icons.ts', + 'build:remix': 'remix build --sourcemap', + 'build:server': 'tsx ./other/build-server.ts', + predev: 'npm run build:icons --silent', + dev: 'remix dev -c "node ./server/dev-server.js" --manual', + 'prisma:studio': 'prisma studio', + format: 'prettier --write .', + lint: 'eslint .', + setup: + 'npm run build && prisma generate && prisma migrate deploy && prisma db seed && playwright install', + start: 'cross-env NODE_ENV=production node .', + 'start:mocks': 'cross-env NODE_ENV=production MOCKS=true tsx .', + test: 'vitest', + coverage: 'nx test --coverage', + 'test:e2e': 'npm run test:e2e:dev --silent', + 'test:e2e:dev': 'playwright test --ui', + 'pretest:e2e:run': 'npm run build', + 'test:e2e:run': 'cross-env CI=true playwright test', + 'test:e2e:install': 'npx playwright install --with-deps chromium', + typecheck: 'tsc', + validate: 'run-p "test -- --run" lint typecheck test:e2e:run', + }, + nx: { + targets: { + 'build:icons': { + outputs: ['{projectRoot}/app/components/ui/icons'], + }, + 'build:remix': { + outputs: ['{projectRoot}/build'], + }, + 'build:server': { + outputs: ['{projectRoot}/server-build'], + }, + test: { + outputs: ['{projectRoot}/test-results'], + }, + 'test:e2e': { + outputs: ['{projectRoot}/playwright-report'], + }, + 'test:e2e:run': { + outputs: ['{projectRoot}/playwright-report'], + }, }, + includedScripts: [], }, - includedScripts: [], }, - }); + {} + ); expect(result.test).toMatchInlineSnapshot(` { "outputs": [ diff --git a/packages/nx/src/utils/package-json.ts b/packages/nx/src/utils/package-json.ts index fd657fc54b172..c586cd812a2aa 100644 --- a/packages/nx/src/utils/package-json.ts +++ b/packages/nx/src/utils/package-json.ts @@ -1,5 +1,6 @@ import { existsSync } from 'fs'; import { dirname, join } from 'path'; +import { NxJsonConfiguration } from '../config/nx-json'; import { ProjectConfiguration, ProjectMetadata, @@ -166,7 +167,10 @@ export function getTagsFromPackageJson(packageJson: PackageJson): string[] { return tags; } -export function readTargetsFromPackageJson(packageJson: PackageJson) { +export function readTargetsFromPackageJson( + packageJson: PackageJson, + nxJson: NxJsonConfiguration +) { const { scripts, nx, private: isPrivate } = packageJson ?? {}; const res: Record = {}; const includedScripts = nx?.includedScripts || Object.keys(scripts ?? {}); @@ -185,12 +189,24 @@ export function readTargetsFromPackageJson(packageJson: PackageJson) { * Add implicit nx-release-publish target for all package.json files that are * not marked as `"private": true` to allow for lightweight configuration for * package based repos. + * + * Any targetDefaults for the nx-release-publish target set by the user should + * be merged with the implicit target. */ if (!isPrivate && !res['nx-release-publish']) { + const nxReleasePublishTargetDefaults = + nxJson?.targetDefaults?.['nx-release-publish'] ?? {}; res['nx-release-publish'] = { - dependsOn: ['^nx-release-publish'], executor: '@nx/js:release-publish', - options: {}, + ...nxReleasePublishTargetDefaults, + dependsOn: [ + // For maximum correctness, projects should only ever be published once their dependencies are successfully published + '^nx-release-publish', + ...(nxReleasePublishTargetDefaults.dependsOn ?? []), + ], + options: { + ...(nxReleasePublishTargetDefaults.options ?? {}), + }, }; }