diff --git a/packages/angular/migrations.json b/packages/angular/migrations.json index 31127ade857be..5eb280a886ae9 100644 --- a/packages/angular/migrations.json +++ b/packages/angular/migrations.json @@ -184,6 +184,12 @@ "version": "15.0.0-beta.0", "description": "Update the usages of @nrwl/angular/testing to import jasmine-marbles symbols from jasmine-marbles itself.", "factory": "./src/migrations/update-15-0-0/switch-to-jasmine-marbles" + }, + "add-karma-inputs": { + "cli": "nx", + "version": "15.0.0-beta.1", + "description": "Stop hashing karma spec files and config files for build targets and dependent tasks", + "factory": "./src/migrations/update-15-0-0/add-karma-inputs" } }, "packageJsonUpdates": { diff --git a/packages/angular/src/migrations/update-15-0-0/add-karma-inputs.spec.ts b/packages/angular/src/migrations/update-15-0-0/add-karma-inputs.spec.ts new file mode 100644 index 0000000000000..2cf067f71817b --- /dev/null +++ b/packages/angular/src/migrations/update-15-0-0/add-karma-inputs.spec.ts @@ -0,0 +1,82 @@ +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { + Tree, + addProjectConfiguration, + readWorkspaceConfiguration, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import addKarmaInputs from './add-karma-inputs'; + +describe('15.0.0 migration (add-karma-inputs)', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should add inputs configuration for karma targets', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + addProjectConfiguration(tree, 'proj', { + root: 'proj', + targets: { + test: { + executor: '@angular-devkit/build-angular:karma', + options: {}, + }, + test2: { + executor: '@angular-devkit/build-angular:karma', + options: {}, + }, + notTest: { + executor: 'nx:run-commands', + }, + }, + }); + tree.write('karma.conf.js', ''); + + await addKarmaInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + "!{projectRoot}/**/*.spec.[jt]s", + "!{projectRoot}/tsconfig.spec.json", + "!{projectRoot}/karma.conf.js", + ], + "sharedGlobals": Array [], + }, + "targetDefaults": Object { + "test": Object { + "inputs": Array [ + "default", + "^production", + "{workspaceRoot}/karma.conf.js", + ], + }, + "test2": Object { + "inputs": Array [ + "default", + "^production", + "{workspaceRoot}/karma.conf.js", + ], + }, + }, + "version": 2, + } + `); + }); +}); diff --git a/packages/angular/src/migrations/update-15-0-0/add-karma-inputs.ts b/packages/angular/src/migrations/update-15-0-0/add-karma-inputs.ts new file mode 100644 index 0000000000000..55560ead5a6c5 --- /dev/null +++ b/packages/angular/src/migrations/update-15-0-0/add-karma-inputs.ts @@ -0,0 +1,60 @@ +import { + formatFiles, + readWorkspaceConfiguration, + Tree, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils'; + +export default async function (tree: Tree) { + const workspaceConfiguration = readWorkspaceConfiguration(tree); + + const karmaTargets = getKarmaTargetNames(tree); + const hasProductionFileset = !!workspaceConfiguration.namedInputs?.production; + + if (karmaTargets.size > 0 && hasProductionFileset) { + const productionFileset = new Set( + workspaceConfiguration.namedInputs.production + ); + for (const exclusion of [ + '!{projectRoot}/**/*.spec.[jt]s', + '!{projectRoot}/tsconfig.spec.json', + '!{projectRoot}/karma.conf.js', + ]) { + productionFileset.add(exclusion); + } + workspaceConfiguration.namedInputs.production = + Array.from(productionFileset); + } + + for (const targetName of karmaTargets) { + workspaceConfiguration.targetDefaults ??= {}; + const jestTargetDefaults = (workspaceConfiguration.targetDefaults[ + targetName + ] ??= {}); + + jestTargetDefaults.inputs ??= [ + 'default', + hasProductionFileset ? '^production' : '^default', + ...(tree.exists('karma.conf.js') + ? ['{workspaceRoot}/karma.conf.js'] + : []), + ]; + } + + updateWorkspaceConfiguration(tree, workspaceConfiguration); + + await formatFiles(tree); +} + +function getKarmaTargetNames(tree: Tree) { + const karmaTargetNames = new Set(); + forEachExecutorOptions( + tree, + '@angular-devkit/build-angular:karma', + (_, __, target) => { + karmaTargetNames.add(target); + } + ); + return karmaTargetNames; +} diff --git a/packages/cypress/migrations.json b/packages/cypress/migrations.json index f4a13b3e8d35a..f76773f548819 100644 --- a/packages/cypress/migrations.json +++ b/packages/cypress/migrations.json @@ -42,6 +42,12 @@ "version": "14.7.0-beta.0", "description": "Update Cypress if using v10 to support latest component testing features", "factory": "./src/migrations/update-14-7-0/update-cypress-version-if-10" + }, + "add-cypress-inputs": { + "cli": "nx", + "version": "15.0.0-beta.0", + "description": "Stop hashing cypress spec files and config files for build targets and dependent tasks", + "factory": "./src/migrations/update-15-0-0/add-cypress-inputs" } }, "packageJsonUpdates": { diff --git a/packages/cypress/src/migrations/update-15-0-0/add-cypress-inputs.spec.ts b/packages/cypress/src/migrations/update-15-0-0/add-cypress-inputs.spec.ts new file mode 100644 index 0000000000000..d6000e1989108 --- /dev/null +++ b/packages/cypress/src/migrations/update-15-0-0/add-cypress-inputs.spec.ts @@ -0,0 +1,145 @@ +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { + Tree, + addProjectConfiguration, + readWorkspaceConfiguration, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import addCypressInputs from './add-cypress-inputs'; + +describe('15.0.0 migration (add-cypress-inputs)', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should add inputs configuration for cypress targets', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + addProjectConfiguration(tree, 'proj', { + root: 'proj', + targets: { + e2e: { + executor: '@nrwl/cypress:cypress', + options: {}, + }, + e2e2: { + executor: '@nrwl/cypress:cypress', + options: {}, + }, + notTest: { + executor: 'nx:run-commands', + }, + }, + }); + tree.write('jest.preset.js', ''); + + await addCypressInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + ], + "sharedGlobals": Array [], + }, + "targetDefaults": Object { + "e2e": Object { + "inputs": Array [ + "default", + "^production", + ], + }, + "e2e2": Object { + "inputs": Array [ + "default", + "^production", + ], + }, + }, + "version": 2, + } + `); + }); + + it('should inputs configuration for cypress component testing targets', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + addProjectConfiguration(tree, 'proj', { + root: 'proj', + targets: { + e2e: { + executor: '@nrwl/cypress:cypress', + options: { + testingType: 'component', + }, + }, + e2e2: { + executor: '@nrwl/cypress:cypress', + options: { + testingType: 'component', + }, + }, + notTest: { + executor: 'nx:run-commands', + }, + }, + }); + tree.write('jest.preset.js', ''); + + await addCypressInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + "!{projectRoot}/cypress/**/*", + "!{projectRoot}/**/*.cy.[jt]s?(x)", + "!{projectRoot}/cypress.config.[jt]s", + ], + "sharedGlobals": Array [], + }, + "targetDefaults": Object { + "e2e": Object { + "inputs": Array [ + "default", + "^production", + ], + }, + "e2e2": Object { + "inputs": Array [ + "default", + "^production", + ], + }, + }, + "version": 2, + } + `); + }); +}); diff --git a/packages/cypress/src/migrations/update-15-0-0/add-cypress-inputs.ts b/packages/cypress/src/migrations/update-15-0-0/add-cypress-inputs.ts new file mode 100644 index 0000000000000..032fc2d8f0354 --- /dev/null +++ b/packages/cypress/src/migrations/update-15-0-0/add-cypress-inputs.ts @@ -0,0 +1,62 @@ +import { + formatFiles, + readWorkspaceConfiguration, + Tree, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils'; +import { CypressExecutorOptions } from '@nrwl/cypress/src/executors/cypress/cypress.impl'; + +export default async function (tree: Tree) { + const workspaceConfiguration = readWorkspaceConfiguration(tree); + + const { cypressTargets, hasComponentTesting } = getCypressTargetNames(tree); + const hasProductionFileset = !!workspaceConfiguration.namedInputs?.production; + + if (hasComponentTesting && hasProductionFileset && cypressTargets.size > 0) { + const productionFileset = new Set( + workspaceConfiguration.namedInputs.production + ); + for (const exclusion of [ + '!{projectRoot}/cypress/**/*', + '!{projectRoot}/**/*.cy.[jt]s?(x)', + '!{projectRoot}/cypress.config.[jt]s', + ]) { + productionFileset.add(exclusion); + } + workspaceConfiguration.namedInputs.production = + Array.from(productionFileset); + } + + for (const targetName of cypressTargets) { + workspaceConfiguration.targetDefaults ??= {}; + const cypressTargetDefaults = (workspaceConfiguration.targetDefaults[ + targetName + ] ??= {}); + + cypressTargetDefaults.inputs ??= [ + 'default', + hasProductionFileset ? '^production' : '^default', + ]; + } + + updateWorkspaceConfiguration(tree, workspaceConfiguration); + + await formatFiles(tree); +} + +function getCypressTargetNames(tree: Tree) { + const cypressTargets = new Set(); + let hasComponentTesting = false; + forEachExecutorOptions( + tree, + '@nrwl/cypress:cypress', + (options, __, target) => { + cypressTargets.add(target); + if (options.testingType === 'component') { + hasComponentTesting = true; + } + } + ); + return { cypressTargets, hasComponentTesting }; +} diff --git a/packages/jest/migrations.json b/packages/jest/migrations.json index 444972308a7e0..be3c7e9712487 100644 --- a/packages/jest/migrations.json +++ b/packages/jest/migrations.json @@ -69,8 +69,14 @@ "update-tests-jest-28": { "version": "14.6.0-beta.0", "cli": "nx", - "description": "Update jest test files to support jest 28 changes (https://jestjs.io/docs/upgrading-to-jest28)", + "description": "Update jest test files to support jest 28 changes (https://jestjs.io/docs/upgrading-to-jest28)", "factory": "./src/migrations/update-14-6-0/update-tests-jest-28" + }, + "add-jest-inputs": { + "version": "15.0.0-beta.0", + "cli": "nx", + "description": "Stop hashing jest spec files and config files for build targets and dependent tasks", + "factory": "./src/migrations/update-15-0-0/add-jest-inputs" } }, "packageJsonUpdates": { diff --git a/packages/jest/src/migrations/update-15-0-0/add-jest-inputs.spec.ts b/packages/jest/src/migrations/update-15-0-0/add-jest-inputs.spec.ts new file mode 100644 index 0000000000000..0c02eaed443a3 --- /dev/null +++ b/packages/jest/src/migrations/update-15-0-0/add-jest-inputs.spec.ts @@ -0,0 +1,82 @@ +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { + Tree, + addProjectConfiguration, + readWorkspaceConfiguration, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import addJestInputs from './add-jest-inputs'; + +describe('15.0.0 migration (add-jest-inputs)', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should add inputs configuration for jest targets', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + addProjectConfiguration(tree, 'proj', { + root: 'proj', + targets: { + test: { + executor: '@nrwl/jest:jest', + options: {}, + }, + test2: { + executor: '@nrwl/jest:jest', + options: {}, + }, + notTest: { + executor: 'nx:run-commands', + }, + }, + }); + tree.write('jest.preset.js', ''); + + await addJestInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", + "!{projectRoot}/tsconfig.spec.json", + "!{projectRoot}/jest.config.[jt]s", + ], + "sharedGlobals": Array [], + }, + "targetDefaults": Object { + "test": Object { + "inputs": Array [ + "default", + "^production", + "{workspaceRoot}/jest.preset.js", + ], + }, + "test2": Object { + "inputs": Array [ + "default", + "^production", + "{workspaceRoot}/jest.preset.js", + ], + }, + }, + "version": 2, + } + `); + }); +}); diff --git a/packages/jest/src/migrations/update-15-0-0/add-jest-inputs.ts b/packages/jest/src/migrations/update-15-0-0/add-jest-inputs.ts new file mode 100644 index 0000000000000..b8f8d4c76db6d --- /dev/null +++ b/packages/jest/src/migrations/update-15-0-0/add-jest-inputs.ts @@ -0,0 +1,56 @@ +import { + formatFiles, + readWorkspaceConfiguration, + Tree, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils'; + +export default async function (tree: Tree) { + const workspaceConfiguration = readWorkspaceConfiguration(tree); + + const jestTargets = getJestTargetNames(tree); + const hasProductionFileset = !!workspaceConfiguration.namedInputs?.production; + + if (jestTargets.size > 0 && hasProductionFileset) { + const productionFileset = new Set( + workspaceConfiguration.namedInputs.production + ); + for (const exclusion of [ + '!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)', + '!{projectRoot}/tsconfig.spec.json', + '!{projectRoot}/jest.config.[jt]s', + ]) { + productionFileset.add(exclusion); + } + workspaceConfiguration.namedInputs.production = + Array.from(productionFileset); + } + + for (const targetName of jestTargets) { + workspaceConfiguration.targetDefaults ??= {}; + const jestTargetDefaults = (workspaceConfiguration.targetDefaults[ + targetName + ] ??= {}); + + jestTargetDefaults.inputs ??= [ + 'default', + hasProductionFileset ? '^production' : '^default', + ...(tree.exists('jest.preset.js') + ? ['{workspaceRoot}/jest.preset.js'] + : []), + ]; + } + + updateWorkspaceConfiguration(tree, workspaceConfiguration); + + await formatFiles(tree); +} + +function getJestTargetNames(tree: Tree) { + const jestTargetNames = new Set(); + forEachExecutorOptions(tree, '@nrwl/jest:jest', (_, __, target) => { + jestTargetNames.add(target); + }); + return jestTargetNames; +} diff --git a/packages/linter/migrations.json b/packages/linter/migrations.json index e8a5287ad0c13..b514e893e6e2d 100644 --- a/packages/linter/migrations.json +++ b/packages/linter/migrations.json @@ -88,6 +88,12 @@ "version": "14.4.4", "description": "Switch from @typescript-eslint/experimental-utils to @typescript-eslint/utils in all rules and rules.spec files", "factory": "./src/migrations/update-14-4-4/experimental-to-utils-rules" + }, + "add-eslint-inputs": { + "cli": "nx", + "version": "15.0.0-beta.0", + "description": "Stop hashing eslint config files for build targets and dependent tasks", + "factory": "./src/migrations/update-15-0-0/add-eslint-inputs" } }, "packageJsonUpdates": { diff --git a/packages/linter/src/migrations/update-15-0-0/add-eslint-inputs.spec.ts b/packages/linter/src/migrations/update-15-0-0/add-eslint-inputs.spec.ts new file mode 100644 index 0000000000000..860a510ddef1c --- /dev/null +++ b/packages/linter/src/migrations/update-15-0-0/add-eslint-inputs.spec.ts @@ -0,0 +1,140 @@ +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { + Tree, + addProjectConfiguration, + readWorkspaceConfiguration, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import addEslintInputs from './add-eslint-inputs'; + +describe('15.0.0 migration (add-eslint-inputs)', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should add inputs configuration for lint targets', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + addProjectConfiguration(tree, 'proj', { + root: 'proj', + targets: { + lint: { + executor: '@nrwl/linter:eslint', + options: {}, + }, + lint2: { + executor: '@nrwl/linter:eslint', + options: {}, + }, + notTest: { + executor: 'nx:run-commands', + }, + }, + }); + tree.write('.eslintrc.json', ''); + + await addEslintInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + "!{projectRoot}/.eslintrc.json", + ], + "sharedGlobals": Array [], + }, + "targetDefaults": Object { + "lint": Object { + "inputs": Array [ + "default", + "{workspaceRoot}/.eslintrc.json", + ], + }, + "lint2": Object { + "inputs": Array [ + "default", + "{workspaceRoot}/.eslintrc.json", + ], + }, + }, + "version": 2, + } + `); + }); + + it('should add inputs configuration for .eslintrc.js', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + addProjectConfiguration(tree, 'proj', { + root: 'proj', + targets: { + lint: { + executor: '@nrwl/linter:eslint', + options: {}, + }, + lint2: { + executor: '@nrwl/linter:eslint', + options: {}, + }, + notTest: { + executor: 'nx:run-commands', + }, + }, + }); + tree.write('.eslintrc.js', ''); + + await addEslintInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + "!{projectRoot}/.eslintrc.json", + ], + "sharedGlobals": Array [], + }, + "targetDefaults": Object { + "lint": Object { + "inputs": Array [ + "default", + "{workspaceRoot}/.eslintrc.js", + ], + }, + "lint2": Object { + "inputs": Array [ + "default", + "{workspaceRoot}/.eslintrc.js", + ], + }, + }, + "version": 2, + } + `); + }); +}); diff --git a/packages/linter/src/migrations/update-15-0-0/add-eslint-inputs.ts b/packages/linter/src/migrations/update-15-0-0/add-eslint-inputs.ts new file mode 100644 index 0000000000000..4f1c66482114a --- /dev/null +++ b/packages/linter/src/migrations/update-15-0-0/add-eslint-inputs.ts @@ -0,0 +1,49 @@ +import { + formatFiles, + readWorkspaceConfiguration, + Tree, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils'; +import { join } from 'path'; + +export default async function (tree: Tree) { + const workspaceConfiguration = readWorkspaceConfiguration(tree); + + const globalEslintFile = ['.eslintrc.js', '.eslintrc.json'].find((file) => + tree.exists(file) + ); + + if (globalEslintFile && workspaceConfiguration.namedInputs?.production) { + const productionFileset = new Set( + workspaceConfiguration.namedInputs.production + ); + productionFileset.add('!{projectRoot}/.eslintrc.json'); + workspaceConfiguration.namedInputs.production = + Array.from(productionFileset); + } + + for (const targetName of getEslintTargets(tree)) { + workspaceConfiguration.targetDefaults ??= {}; + const lintTargetDefaults = (workspaceConfiguration.targetDefaults[ + targetName + ] ??= {}); + + lintTargetDefaults.inputs ??= [ + 'default', + ...(globalEslintFile ? [join('{workspaceRoot}', globalEslintFile)] : []), + ]; + } + + updateWorkspaceConfiguration(tree, workspaceConfiguration); + + await formatFiles(tree); +} + +function getEslintTargets(tree: Tree) { + const eslintTargetNames = new Set(); + forEachExecutorOptions(tree, '@nrwl/linter:eslint', (_, __, target) => { + eslintTargetNames.add(target); + }); + return eslintTargetNames; +} diff --git a/packages/nx/migrations.json b/packages/nx/migrations.json index 121e1104d0c55..ee4f83387a436 100644 --- a/packages/nx/migrations.json +++ b/packages/nx/migrations.json @@ -29,6 +29,12 @@ "version": "14.3.4-beta.1", "description": "Replace targetDependencies with targetDefaults", "implementation": "./src/migrations/update-14-3-4/create-target-defaults" + }, + "15.0.0-migrate-to-inputs": { + "cli": "nx", + "version": "15.0.0-beta.1", + "description": "Replace implicitDependencies with namedInputs + target inputs", + "implementation": "./src/migrations/update-15-0-0/migrate-to-inputs" } } } diff --git a/packages/nx/migrations.spec.ts b/packages/nx/migrations.spec.ts new file mode 100644 index 0000000000000..10d3af6028fd3 --- /dev/null +++ b/packages/nx/migrations.spec.ts @@ -0,0 +1,12 @@ +import path = require('path'); +import json = require('./migrations.json'); + +describe('Nx migrations', () => { + it('should have valid paths', () => { + Object.values(json.generators).forEach((m) => { + expect(() => + require.resolve(path.join(__dirname, `${m.implementation}.ts`)) + ).not.toThrow(); + }); + }); +}); diff --git a/packages/nx/src/generators/utils/project-configuration.spec.ts b/packages/nx/src/generators/utils/project-configuration.spec.ts index b48a73bb3858b..5a509ba1f0e9f 100644 --- a/packages/nx/src/generators/utils/project-configuration.spec.ts +++ b/packages/nx/src/generators/utils/project-configuration.spec.ts @@ -104,6 +104,27 @@ describe('project configuration', () => { }); }); + describe('readWorkspaceConfiguration', () => { + it('should read the workspace configuration', () => { + const result = readWorkspaceConfiguration(tree); + expect(result).toEqual({ + affected: { + defaultBase: 'main', + }, + npmScope: 'proj', + tasksRunnerOptions: { + default: { + options: { + cacheableOperations: ['build', 'lint', 'test', 'e2e'], + }, + runner: 'nx/tasks-runners/default', + }, + }, + version: 1, + }); + }); + }); + describe('updateWorkspaceConfiguration', () => { let workspaceConfiguration: WorkspaceConfiguration; @@ -387,6 +408,27 @@ describe('project configuration', () => { }); }); + describe('readWorkspaceConfiguration', () => { + it('should read the workspace configuration', () => { + const result = readWorkspaceConfiguration(tree); + expect(result).toEqual({ + affected: { + defaultBase: 'main', + }, + npmScope: 'proj', + tasksRunnerOptions: { + default: { + options: { + cacheableOperations: ['build', 'lint', 'test', 'e2e'], + }, + runner: 'nx/tasks-runners/default', + }, + }, + version: 2, + }); + }); + }); + describe('updateWorkspaceConfiguration', () => { let workspaceConfiguration: WorkspaceConfiguration; @@ -611,6 +653,20 @@ describe('project configuration', () => { tree = createTree(); }); + describe('readWorkspaceConfiguration', () => { + it('should read project configuration from package.json files', () => { + writeJson(tree, 'proj/package.json', { + name: 'proj', + }); + + const workspace = readWorkspaceConfiguration(tree); + + expect(workspace).toEqual({ + version: 2, + }); + }); + }); + describe('readProjectConfiguration', () => { it('should read project configuration from package.json files', () => { writeJson(tree, 'proj/package.json', { diff --git a/packages/nx/src/migrations/update-15-0-0/migrate-to-inputs.spec.ts b/packages/nx/src/migrations/update-15-0-0/migrate-to-inputs.spec.ts new file mode 100644 index 0000000000000..e3771998d51bf --- /dev/null +++ b/packages/nx/src/migrations/update-15-0-0/migrate-to-inputs.spec.ts @@ -0,0 +1,256 @@ +import { createTreeWithEmptyWorkspace } from '../../generators/testing-utils/create-tree-with-empty-workspace'; +import type { Tree } from '../../generators/tree'; +import { + addProjectConfiguration, + readProjectConfiguration, + readWorkspaceConfiguration, + updateWorkspaceConfiguration, + WorkspaceConfiguration, +} from '../../generators/utils/project-configuration'; +import migrateToInputs from './migrate-to-inputs'; + +describe('15.0.0 migration (migrate-to-inputs)', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should add build inputs configuration to inputs', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + targetDefaults: { + build: { + dependsOn: ['^build'], + }, + }, + implicitDependencies: { + '.eslintrc.json': '*', + }, + }); + addProjectConfiguration(tree, 'proj', { + root: 'proj', + targets: { + build: { + executor: 'nx:run-commands', + options: {}, + }, + }, + }); + await migrateToInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + + expect(updated.implicitDependencies).toBeUndefined(); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + ], + "sharedGlobals": Array [], + }, + "targetDefaults": Object { + "build": Object { + "dependsOn": Array [ + "^build", + ], + "inputs": Array [ + "production", + "^production", + ], + }, + }, + "version": 2, + } + `); + }); + + it('should not add build inputs configuration to inputs', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + targetDefaults: { + prepare: { + dependsOn: ['^prepare'], + }, + }, + implicitDependencies: { + '.eslintrc.json': '*', + }, + }); + addProjectConfiguration(tree, 'proj', { + root: 'proj', + targets: { + nobuild: { + executor: 'nx:run-commands', + options: {}, + }, + }, + }); + await migrateToInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + + expect(updated.implicitDependencies).toBeUndefined(); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + ], + "sharedGlobals": Array [], + }, + "targetDefaults": Object { + "prepare": Object { + "dependsOn": Array [ + "^prepare", + ], + }, + }, + "version": 2, + } + `); + }); + + it('should add implicitDependencies that affect all projects to sharedGlobals', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + implicitDependencies: { + Jenkinsfile: '*', + }, + }); + await migrateToInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + + expect(updated.implicitDependencies).toBeUndefined(); + expect(updated.namedInputs.sharedGlobals).toContain( + '{workspaceRoot}/Jenkinsfile' + ); + }); + + it('should not add package.json to filesets', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + implicitDependencies: { + 'package.json': { + dependencies: '*', + devDependencies: '*', + }, + }, + }); + await migrateToInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + + expect(updated.implicitDependencies).toBeUndefined(); + expect(updated.namedInputs.sharedGlobals).toEqual([]); + }); + + it('should handle other .json files', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + implicitDependencies: { + 'config.json': { + important: '*', + }, + }, + }); + await migrateToInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + + expect(updated.implicitDependencies).toBeUndefined(); + expect(updated.namedInputs.sharedGlobals).toContain( + '{workspaceRoot}/config.json' + ); + }); + + it('should add project specific implicit dependencies to project namedInputs', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + implicitDependencies: { + 'tools/scripts/build-app.js': ['app1', 'app2'], + }, + }); + addProjectConfiguration(tree, 'app1', { + root: 'app1', + }); + addProjectConfiguration(tree, 'app2', { + root: 'app2', + }); + addProjectConfiguration(tree, 'lib1', { + root: 'lib1', + }); + + await migrateToInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated.implicitDependencies).toBeUndefined(); + expect(updated.namedInputs.projectSpecificFiles).toEqual([]); + expect(updated.namedInputs.default).toContain('projectSpecificFiles'); + + const app1 = readProjectConfiguration(tree, 'app1'); + expect(app1.namedInputs.projectSpecificFiles).toContain( + '{workspaceRoot}/tools/scripts/build-app.js' + ); + const app2 = readProjectConfiguration(tree, 'app2'); + expect(app2.namedInputs.projectSpecificFiles).toContain( + '{workspaceRoot}/tools/scripts/build-app.js' + ); + + const lib = readProjectConfiguration(tree, 'lib1'); + expect(lib.namedInputs).toBeUndefined(); + }); + + it('should do nothing if there are no implicitDependencies', async () => { + const workspace: WorkspaceConfiguration = { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*'], + production: ['default'], + sharedGlobals: ['babel.config.json'], + }, + targetDefaults: { + build: { + dependsOn: ['^build'], + inputs: ['default', '^default'], + }, + }, + }; + updateWorkspaceConfiguration(tree, workspace); + + await migrateToInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated.implicitDependencies).toBeUndefined(); + expect(updated).toEqual(workspace); + }); + + it('should not make any changes if there is no nx.json', async () => { + tree.delete('nx.json'); + + await migrateToInputs(tree); + + expect(tree.exists('nx.json')).toEqual(false); + }); + + it('should not make any changes if the workspace extends npm.json', async () => { + const workspace = readWorkspaceConfiguration(tree); + workspace.extends = 'nx/presets/npm.json'; + updateWorkspaceConfiguration(tree, workspace); + + await migrateToInputs(tree); + + const updatedWorkspace = readWorkspaceConfiguration(tree); + expect(updatedWorkspace.namedInputs).not.toBeDefined(); + }); +}); diff --git a/packages/nx/src/migrations/update-15-0-0/migrate-to-inputs.ts b/packages/nx/src/migrations/update-15-0-0/migrate-to-inputs.ts new file mode 100644 index 0000000000000..d3e018af0247b --- /dev/null +++ b/packages/nx/src/migrations/update-15-0-0/migrate-to-inputs.ts @@ -0,0 +1,106 @@ +import { Tree } from '../../generators/tree'; +import { formatChangedFilesWithPrettierIfAvailable } from '../../generators/internal-utils/format-changed-files-with-prettier-if-available'; +import { + getProjects, + readWorkspaceConfiguration, + updateProjectConfiguration, + updateWorkspaceConfiguration, +} from '../../generators/utils/project-configuration'; +import { join } from 'path'; + +const skippedFiles = [ + 'package.json', // Not to be added to filesets + 'babel.config.json', // Will be handled by various plugins + 'karma.conf.js', // Will be handled by @nrwl/angular + 'jest.preset.js', // Will be handled by @nrwl/jest + '.storybook', // Will be handled by @nrwl/storybook + // Will be handled by @nrwl/linter + '.eslintrc.json', + '.eslintrc.js', +]; + +export default async function (tree: Tree) { + // If the workspace doesn't have a nx.json, don't make any changes + if (!tree.exists('nx.json')) { + return; + } + + const workspaceConfiguration = readWorkspaceConfiguration(tree); + + // If this is a npm workspace, don't make any changes + if (workspaceConfiguration.extends === 'nx/presets/npm.json') { + return; + } + + workspaceConfiguration.namedInputs ??= { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }; + + if (isBuildATarget(tree)) { + workspaceConfiguration.targetDefaults ??= {}; + workspaceConfiguration.targetDefaults.build ??= {}; + workspaceConfiguration.targetDefaults.build.inputs ??= [ + 'production', + '^production', + ]; + } + + if (workspaceConfiguration.implicitDependencies) { + const projects = getProjects(tree); + + for (const [files, dependents] of Object.entries( + workspaceConfiguration.implicitDependencies + )) { + // Skip these because other plugins take care of them + if (skippedFiles.includes(files)) { + continue; + } else if (Array.isArray(dependents)) { + workspaceConfiguration.namedInputs.projectSpecificFiles = []; + const defaultFileset = new Set( + workspaceConfiguration.namedInputs.default ?? [ + '{projectRoot}/**/*', + 'sharedGlobals', + ] + ); + defaultFileset.add('projectSpecificFiles'); + workspaceConfiguration.namedInputs.default = Array.from(defaultFileset); + + for (const dependent of dependents) { + const project = projects.get(dependent); + project.namedInputs ??= {}; + const projectSpecificFileset = new Set( + project.namedInputs.projectSpecificFiles ?? [] + ); + projectSpecificFileset.add(join('{workspaceRoot}', files)); + project.namedInputs.projectSpecificFiles = Array.from( + projectSpecificFileset + ); + updateProjectConfiguration(tree, dependent, project); + } + } else { + workspaceConfiguration.namedInputs.sharedGlobals.push( + join('{workspaceRoot}', files) + ); + } + } + delete workspaceConfiguration.implicitDependencies; + } + + updateWorkspaceConfiguration(tree, workspaceConfiguration); + + await formatChangedFilesWithPrettierIfAvailable(tree); +} + +function isBuildATarget(tree: Tree) { + const projects = getProjects(tree); + + for (const [_, project] of projects) { + if (project.targets?.build) { + return true; + } + } + + return false; +} diff --git a/packages/react-native/migrations.json b/packages/react-native/migrations.json index 71253de587d54..1b23df92b52b1 100644 --- a/packages/react-native/migrations.json +++ b/packages/react-native/migrations.json @@ -65,6 +65,12 @@ "cli": "nx", "description": "Remove transform in jest.config", "factory": "./src/migrations/update-14-6-0/remove-jest-transform" + }, + "add-babel-inputs": { + "cli": "nx", + "version": "15.0.0-beta.0", + "description": "Adds babel.config.json to the hash of all tasks", + "factory": "./src/migrations/update-15-0-0/add-babel-inputs" } }, "packageJsonUpdates": { diff --git a/packages/react-native/src/migrations/update-15-0-0/add-babel-inputs.spec.ts b/packages/react-native/src/migrations/update-15-0-0/add-babel-inputs.spec.ts new file mode 100644 index 0000000000000..1b2d86eb1c72f --- /dev/null +++ b/packages/react-native/src/migrations/update-15-0-0/add-babel-inputs.spec.ts @@ -0,0 +1,81 @@ +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { + Tree, + readWorkspaceConfiguration, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import addBabelInputs from './add-babel-inputs'; + +describe('15.0.0 migration (add-babel-inputs)', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should add babel.config.json to sharedGlobals', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + tree.write('babel.config.json', ''); + + await addBabelInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + ], + "sharedGlobals": Array [ + "{workspaceRoot}/babel.config.json", + ], + }, + "version": 2, + } + `); + }); + + it('should add babel.config.js', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + tree.write('babel.config.js', ''); + + await addBabelInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + ], + "sharedGlobals": Array [ + "{workspaceRoot}/babel.config.js", + ], + }, + "version": 2, + } + `); + }); +}); diff --git a/packages/react-native/src/migrations/update-15-0-0/add-babel-inputs.ts b/packages/react-native/src/migrations/update-15-0-0/add-babel-inputs.ts new file mode 100644 index 0000000000000..42b4b2da93362 --- /dev/null +++ b/packages/react-native/src/migrations/update-15-0-0/add-babel-inputs.ts @@ -0,0 +1,28 @@ +import { + formatFiles, + readWorkspaceConfiguration, + Tree, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import { join } from 'path'; + +export default async function (tree: Tree) { + const workspaceConfiguration = readWorkspaceConfiguration(tree); + + const globalBabelFile = ['babel.config.js', 'babel.config.json'].find( + (file) => tree.exists(file) + ); + + if (globalBabelFile && workspaceConfiguration.namedInputs?.sharedGlobals) { + const sharedGlobalFileset = new Set( + workspaceConfiguration.namedInputs.sharedGlobals + ); + sharedGlobalFileset.add(join('{workspaceRoot}', globalBabelFile)); + workspaceConfiguration.namedInputs.sharedGlobals = + Array.from(sharedGlobalFileset); + } + + updateWorkspaceConfiguration(tree, workspaceConfiguration); + + await formatFiles(tree); +} diff --git a/packages/rollup/migrations.json b/packages/rollup/migrations.json new file mode 100644 index 0000000000000..75899c88e2055 --- /dev/null +++ b/packages/rollup/migrations.json @@ -0,0 +1,11 @@ +{ + "schematics": { + "add-babel-inputs": { + "cli": "nx", + "version": "15.0.0-beta.0", + "description": "Adds babel.config.json to the hash of all tasks", + "factory": "./src/migrations/update-15-0-0/add-babel-inputs" + } + }, + "packageJsonUpdates": {} +} diff --git a/packages/rollup/migrations.spec.ts b/packages/rollup/migrations.spec.ts new file mode 100644 index 0000000000000..52bd6519f766a --- /dev/null +++ b/packages/rollup/migrations.spec.ts @@ -0,0 +1,12 @@ +import path = require('path'); +import json = require('./migrations.json'); + +describe('Rollup migrations', () => { + it('should have valid paths', () => { + Object.values(json.schematics).forEach((m) => { + expect(() => + require.resolve(path.join(__dirname, `${m.factory}.ts`)) + ).not.toThrow(); + }); + }); +}); diff --git a/packages/rollup/src/migrations/update-15-0-0/add-babel-inputs.spec.ts b/packages/rollup/src/migrations/update-15-0-0/add-babel-inputs.spec.ts new file mode 100644 index 0000000000000..1b2d86eb1c72f --- /dev/null +++ b/packages/rollup/src/migrations/update-15-0-0/add-babel-inputs.spec.ts @@ -0,0 +1,81 @@ +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { + Tree, + readWorkspaceConfiguration, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import addBabelInputs from './add-babel-inputs'; + +describe('15.0.0 migration (add-babel-inputs)', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should add babel.config.json to sharedGlobals', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + tree.write('babel.config.json', ''); + + await addBabelInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + ], + "sharedGlobals": Array [ + "{workspaceRoot}/babel.config.json", + ], + }, + "version": 2, + } + `); + }); + + it('should add babel.config.js', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + tree.write('babel.config.js', ''); + + await addBabelInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + ], + "sharedGlobals": Array [ + "{workspaceRoot}/babel.config.js", + ], + }, + "version": 2, + } + `); + }); +}); diff --git a/packages/rollup/src/migrations/update-15-0-0/add-babel-inputs.ts b/packages/rollup/src/migrations/update-15-0-0/add-babel-inputs.ts new file mode 100644 index 0000000000000..42b4b2da93362 --- /dev/null +++ b/packages/rollup/src/migrations/update-15-0-0/add-babel-inputs.ts @@ -0,0 +1,28 @@ +import { + formatFiles, + readWorkspaceConfiguration, + Tree, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import { join } from 'path'; + +export default async function (tree: Tree) { + const workspaceConfiguration = readWorkspaceConfiguration(tree); + + const globalBabelFile = ['babel.config.js', 'babel.config.json'].find( + (file) => tree.exists(file) + ); + + if (globalBabelFile && workspaceConfiguration.namedInputs?.sharedGlobals) { + const sharedGlobalFileset = new Set( + workspaceConfiguration.namedInputs.sharedGlobals + ); + sharedGlobalFileset.add(join('{workspaceRoot}', globalBabelFile)); + workspaceConfiguration.namedInputs.sharedGlobals = + Array.from(sharedGlobalFileset); + } + + updateWorkspaceConfiguration(tree, workspaceConfiguration); + + await formatFiles(tree); +} diff --git a/packages/storybook/migrations.json b/packages/storybook/migrations.json index 7962ca30b3875..b91f107b7c1f9 100644 --- a/packages/storybook/migrations.json +++ b/packages/storybook/migrations.json @@ -79,6 +79,12 @@ "cli": "nx", "description": "Change storybook targets for Angular projects to use @storybook/angular executors", "factory": "./src/migrations/update-14-1-8/change-storybook-targets" + }, + "update-15.0.8-add-storybook-inputs": { + "cli": "nx", + "version": "15.0.0-beta.0", + "description": "Stop hashing storybook config files and story files for build targets and dependent tasks", + "factory": "./src/migrations/update-15-0-0/add-storybook-inputs" } }, "packageJsonUpdates": { diff --git a/packages/storybook/src/migrations/update-15-0-0/add-storybook-inputs.spec.ts b/packages/storybook/src/migrations/update-15-0-0/add-storybook-inputs.spec.ts new file mode 100644 index 0000000000000..b69848306c91b --- /dev/null +++ b/packages/storybook/src/migrations/update-15-0-0/add-storybook-inputs.spec.ts @@ -0,0 +1,146 @@ +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { + Tree, + addProjectConfiguration, + readWorkspaceConfiguration, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import addStorybookInputs from './add-storybook-inputs'; + +describe('15.0.0 migration (add-storybook-inputs)', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should add inputs configuration for storybook targets', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + addProjectConfiguration(tree, 'proj', { + root: 'proj', + targets: { + 'build-storybook': { + executor: '@nrwl/storybook:build', + options: {}, + }, + 'build-storybook2': { + executor: '@nrwl/storybook:build', + options: {}, + }, + notStorybook: { + executor: 'nx:run-commands', + }, + }, + }); + tree.write('.storybook/main.js', ''); + + await addStorybookInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + "!{projectRoot}/.storybook/**/*", + "!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)", + ], + "sharedGlobals": Array [], + }, + "targetDefaults": Object { + "build-storybook": Object { + "inputs": Array [ + "default", + "^production", + "{workspaceRoot}/.storybook/**/*", + ], + }, + "build-storybook2": Object { + "inputs": Array [ + "default", + "^production", + "{workspaceRoot}/.storybook/**/*", + ], + }, + }, + "version": 2, + } + `); + }); + + it('should add inputs configuration for angular storybook targets', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + addProjectConfiguration(tree, 'proj', { + root: 'proj', + targets: { + 'build-storybook': { + executor: '@storybook/angular:build-storybook', + options: {}, + }, + 'build-storybook2': { + executor: '@storybook/angular:build-storybook', + options: {}, + }, + notStorybook: { + executor: 'nx:run-commands', + }, + }, + }); + tree.write('.storybook/main.js', ''); + + await addStorybookInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + "!{projectRoot}/.storybook/**/*", + "!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)", + ], + "sharedGlobals": Array [], + }, + "targetDefaults": Object { + "build-storybook": Object { + "inputs": Array [ + "default", + "^production", + "{workspaceRoot}/.storybook/**/*", + ], + }, + "build-storybook2": Object { + "inputs": Array [ + "default", + "^production", + "{workspaceRoot}/.storybook/**/*", + ], + }, + }, + "version": 2, + } + `); + }); +}); diff --git a/packages/storybook/src/migrations/update-15-0-0/add-storybook-inputs.ts b/packages/storybook/src/migrations/update-15-0-0/add-storybook-inputs.ts new file mode 100644 index 0000000000000..0ca8c4580e3a9 --- /dev/null +++ b/packages/storybook/src/migrations/update-15-0-0/add-storybook-inputs.ts @@ -0,0 +1,60 @@ +import { + formatFiles, + readWorkspaceConfiguration, + Tree, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils'; + +export default async function (tree: Tree) { + const workspaceConfiguration = readWorkspaceConfiguration(tree); + + const storybookTargets = getStorybookBuildTargets(tree); + const hasProductionFileset = !!workspaceConfiguration.namedInputs?.production; + + if (storybookTargets.size > 0 && hasProductionFileset) { + const productionFileset = new Set( + workspaceConfiguration.namedInputs.production + ); + for (const exclusion of [ + '!{projectRoot}/.storybook/**/*', + '!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)', + ]) { + productionFileset.add(exclusion); + } + workspaceConfiguration.namedInputs.production = + Array.from(productionFileset); + } + + for (const targetName of storybookTargets) { + workspaceConfiguration.targetDefaults ??= {}; + const storybookTargetDefaults = (workspaceConfiguration.targetDefaults[ + targetName + ] ??= {}); + + storybookTargetDefaults.inputs ??= [ + 'default', + hasProductionFileset ? '^production' : '^default', + ...(tree.exists('.storybook') ? ['{workspaceRoot}/.storybook/**/*'] : []), + ]; + } + + updateWorkspaceConfiguration(tree, workspaceConfiguration); + + await formatFiles(tree); +} + +function getStorybookBuildTargets(tree: Tree) { + const storybookBuildTargets = new Set(); + forEachExecutorOptions(tree, '@nrwl/storybook:build', (_, __, target) => { + storybookBuildTargets.add(target); + }); + forEachExecutorOptions( + tree, + '@storybook/angular:build-storybook', + (_, __, target) => { + storybookBuildTargets.add(target); + } + ); + return storybookBuildTargets; +} diff --git a/packages/web/migrations.json b/packages/web/migrations.json index e3713bfbe72af..f8d7b7421d8e2 100644 --- a/packages/web/migrations.json +++ b/packages/web/migrations.json @@ -68,6 +68,12 @@ "version": "14.7.6-beta.1", "description": "Update usages of webpack executors to @nrwl/webpack", "factory": "./src/migrations/update-14-7-6/update-webpack-executor" + }, + "add-babel-inputs": { + "cli": "nx", + "version": "15.0.0-beta.0", + "description": "Adds babel.config.json to the hash of all tasks", + "factory": "./src/migrations/update-15-0-0/add-babel-inputs" } }, "packageJsonUpdates": { diff --git a/packages/web/src/migrations/update-15-0-0/add-babel-inputs.spec.ts b/packages/web/src/migrations/update-15-0-0/add-babel-inputs.spec.ts new file mode 100644 index 0000000000000..1b2d86eb1c72f --- /dev/null +++ b/packages/web/src/migrations/update-15-0-0/add-babel-inputs.spec.ts @@ -0,0 +1,81 @@ +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { + Tree, + readWorkspaceConfiguration, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import addBabelInputs from './add-babel-inputs'; + +describe('15.0.0 migration (add-babel-inputs)', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should add babel.config.json to sharedGlobals', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + tree.write('babel.config.json', ''); + + await addBabelInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + ], + "sharedGlobals": Array [ + "{workspaceRoot}/babel.config.json", + ], + }, + "version": 2, + } + `); + }); + + it('should add babel.config.js', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + tree.write('babel.config.js', ''); + + await addBabelInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + ], + "sharedGlobals": Array [ + "{workspaceRoot}/babel.config.js", + ], + }, + "version": 2, + } + `); + }); +}); diff --git a/packages/web/src/migrations/update-15-0-0/add-babel-inputs.ts b/packages/web/src/migrations/update-15-0-0/add-babel-inputs.ts new file mode 100644 index 0000000000000..42b4b2da93362 --- /dev/null +++ b/packages/web/src/migrations/update-15-0-0/add-babel-inputs.ts @@ -0,0 +1,28 @@ +import { + formatFiles, + readWorkspaceConfiguration, + Tree, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import { join } from 'path'; + +export default async function (tree: Tree) { + const workspaceConfiguration = readWorkspaceConfiguration(tree); + + const globalBabelFile = ['babel.config.js', 'babel.config.json'].find( + (file) => tree.exists(file) + ); + + if (globalBabelFile && workspaceConfiguration.namedInputs?.sharedGlobals) { + const sharedGlobalFileset = new Set( + workspaceConfiguration.namedInputs.sharedGlobals + ); + sharedGlobalFileset.add(join('{workspaceRoot}', globalBabelFile)); + workspaceConfiguration.namedInputs.sharedGlobals = + Array.from(sharedGlobalFileset); + } + + updateWorkspaceConfiguration(tree, workspaceConfiguration); + + await formatFiles(tree); +} diff --git a/packages/webpack/migrations.json b/packages/webpack/migrations.json new file mode 100644 index 0000000000000..75899c88e2055 --- /dev/null +++ b/packages/webpack/migrations.json @@ -0,0 +1,11 @@ +{ + "schematics": { + "add-babel-inputs": { + "cli": "nx", + "version": "15.0.0-beta.0", + "description": "Adds babel.config.json to the hash of all tasks", + "factory": "./src/migrations/update-15-0-0/add-babel-inputs" + } + }, + "packageJsonUpdates": {} +} diff --git a/packages/webpack/migrations.spec.ts b/packages/webpack/migrations.spec.ts new file mode 100644 index 0000000000000..f6e94c98d4105 --- /dev/null +++ b/packages/webpack/migrations.spec.ts @@ -0,0 +1,12 @@ +import path = require('path'); +import json = require('./migrations.json'); + +describe('Webpack migrations', () => { + it('should have valid paths', () => { + Object.values(json.schematics).forEach((m) => { + expect(() => + require.resolve(path.join(__dirname, `${m.factory}.ts`)) + ).not.toThrow(); + }); + }); +}); diff --git a/packages/webpack/src/migrations/update-15-0-0/add-babel-inputs.spec.ts b/packages/webpack/src/migrations/update-15-0-0/add-babel-inputs.spec.ts new file mode 100644 index 0000000000000..1b2d86eb1c72f --- /dev/null +++ b/packages/webpack/src/migrations/update-15-0-0/add-babel-inputs.spec.ts @@ -0,0 +1,81 @@ +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { + Tree, + readWorkspaceConfiguration, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import addBabelInputs from './add-babel-inputs'; + +describe('15.0.0 migration (add-babel-inputs)', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should add babel.config.json to sharedGlobals', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + tree.write('babel.config.json', ''); + + await addBabelInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + ], + "sharedGlobals": Array [ + "{workspaceRoot}/babel.config.json", + ], + }, + "version": 2, + } + `); + }); + + it('should add babel.config.js', async () => { + updateWorkspaceConfiguration(tree, { + version: 2, + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + sharedGlobals: [], + production: ['default'], + }, + }); + tree.write('babel.config.js', ''); + + await addBabelInputs(tree); + + const updated = readWorkspaceConfiguration(tree); + expect(updated).toMatchInlineSnapshot(` + Object { + "namedInputs": Object { + "default": Array [ + "{projectRoot}/**/*", + "sharedGlobals", + ], + "production": Array [ + "default", + ], + "sharedGlobals": Array [ + "{workspaceRoot}/babel.config.js", + ], + }, + "version": 2, + } + `); + }); +}); diff --git a/packages/webpack/src/migrations/update-15-0-0/add-babel-inputs.ts b/packages/webpack/src/migrations/update-15-0-0/add-babel-inputs.ts new file mode 100644 index 0000000000000..42b4b2da93362 --- /dev/null +++ b/packages/webpack/src/migrations/update-15-0-0/add-babel-inputs.ts @@ -0,0 +1,28 @@ +import { + formatFiles, + readWorkspaceConfiguration, + Tree, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import { join } from 'path'; + +export default async function (tree: Tree) { + const workspaceConfiguration = readWorkspaceConfiguration(tree); + + const globalBabelFile = ['babel.config.js', 'babel.config.json'].find( + (file) => tree.exists(file) + ); + + if (globalBabelFile && workspaceConfiguration.namedInputs?.sharedGlobals) { + const sharedGlobalFileset = new Set( + workspaceConfiguration.namedInputs.sharedGlobals + ); + sharedGlobalFileset.add(join('{workspaceRoot}', globalBabelFile)); + workspaceConfiguration.namedInputs.sharedGlobals = + Array.from(sharedGlobalFileset); + } + + updateWorkspaceConfiguration(tree, workspaceConfiguration); + + await formatFiles(tree); +} diff --git a/packages/workspace/presets/core.json b/packages/workspace/presets/core.json index b57cee16a5f20..106dd457931cf 100644 --- a/packages/workspace/presets/core.json +++ b/packages/workspace/presets/core.json @@ -1,13 +1,8 @@ { - "implicitDependencies": { - "package.json": { - "dependencies": "*", - "devDependencies": "*" - }, - ".eslintrc.json": "*" - }, - "targetDependencies": { - "build": ["^build"] + "targetDefaults": { + "build": { + "dependsOn": ["^build"] + } }, "workspaceLayout": { "appsDir": "packages", diff --git a/packages/workspace/presets/npm.json b/packages/workspace/presets/npm.json index 060d46fc5db0f..6ab4d14d52222 100644 --- a/packages/workspace/presets/npm.json +++ b/packages/workspace/presets/npm.json @@ -1,13 +1,8 @@ { - "implicitDependencies": { - "package.json": { - "dependencies": "*", - "devDependencies": "*" - }, - ".eslintrc.json": "*" - }, - "targetDependencies": { - "build": ["^build"] + "targetDefaults": { + "build": { + "dependsOn": ["^build"] + } }, "workspaceLayout": { "libsDir": "packages"