diff --git a/docs/angular/api-workspace/npmscripts/affected-apps.md b/docs/angular/api-workspace/npmscripts/affected-apps.md index 5ed138ef57..2fd303e55b 100644 --- a/docs/angular/api-workspace/npmscripts/affected-apps.md +++ b/docs/angular/api-workspace/npmscripts/affected-apps.md @@ -40,6 +40,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -68,6 +72,10 @@ Isolate projects which previously failed Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/angular/api-workspace/npmscripts/affected-build.md b/docs/angular/api-workspace/npmscripts/affected-build.md index 8c00065fb8..fa98ccca64 100644 --- a/docs/angular/api-workspace/npmscripts/affected-build.md +++ b/docs/angular/api-workspace/npmscripts/affected-build.md @@ -58,6 +58,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -98,6 +102,10 @@ Parallelize the command Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/angular/api-workspace/npmscripts/affected-dep-graph.md b/docs/angular/api-workspace/npmscripts/affected-dep-graph.md index 1631164a5c..95b7d21807 100644 --- a/docs/angular/api-workspace/npmscripts/affected-dep-graph.md +++ b/docs/angular/api-workspace/npmscripts/affected-dep-graph.md @@ -46,6 +46,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -78,6 +82,10 @@ Isolate projects which previously failed Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/angular/api-workspace/npmscripts/affected-e2e.md b/docs/angular/api-workspace/npmscripts/affected-e2e.md index 1fc3577976..e2b3d59c31 100644 --- a/docs/angular/api-workspace/npmscripts/affected-e2e.md +++ b/docs/angular/api-workspace/npmscripts/affected-e2e.md @@ -58,6 +58,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -98,6 +102,10 @@ Parallelize the command Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/angular/api-workspace/npmscripts/affected-libs.md b/docs/angular/api-workspace/npmscripts/affected-libs.md index 379d1e1afa..6196a31aac 100644 --- a/docs/angular/api-workspace/npmscripts/affected-libs.md +++ b/docs/angular/api-workspace/npmscripts/affected-libs.md @@ -40,6 +40,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -68,6 +72,10 @@ Isolate projects which previously failed Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/angular/api-workspace/npmscripts/affected-lint.md b/docs/angular/api-workspace/npmscripts/affected-lint.md index da208ac511..b67cee8e03 100644 --- a/docs/angular/api-workspace/npmscripts/affected-lint.md +++ b/docs/angular/api-workspace/npmscripts/affected-lint.md @@ -58,6 +58,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -98,6 +102,10 @@ Parallelize the command Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/angular/api-workspace/npmscripts/affected-test.md b/docs/angular/api-workspace/npmscripts/affected-test.md index ffbb5b8c75..125b641c10 100644 --- a/docs/angular/api-workspace/npmscripts/affected-test.md +++ b/docs/angular/api-workspace/npmscripts/affected-test.md @@ -58,6 +58,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -98,6 +102,10 @@ Parallelize the command Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/angular/api-workspace/npmscripts/affected.md b/docs/angular/api-workspace/npmscripts/affected.md index d7bcc0ef70..e79e2a65a1 100644 --- a/docs/angular/api-workspace/npmscripts/affected.md +++ b/docs/angular/api-workspace/npmscripts/affected.md @@ -64,6 +64,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -104,6 +108,10 @@ Parallelize the command Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### target Task to run for affected projects diff --git a/docs/angular/api-workspace/npmscripts/format-check.md b/docs/angular/api-workspace/npmscripts/format-check.md index 8d7c53c5be..e0243eeb1b 100644 --- a/docs/angular/api-workspace/npmscripts/format-check.md +++ b/docs/angular/api-workspace/npmscripts/format-check.md @@ -22,6 +22,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -50,6 +54,10 @@ Isolate projects which previously failed Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/angular/api-workspace/npmscripts/format-write.md b/docs/angular/api-workspace/npmscripts/format-write.md index 4d75c4e5a2..c498cc2449 100644 --- a/docs/angular/api-workspace/npmscripts/format-write.md +++ b/docs/angular/api-workspace/npmscripts/format-write.md @@ -22,6 +22,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -50,6 +54,10 @@ Isolate projects which previously failed Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/react/api-workspace/npmscripts/affected-apps.md b/docs/react/api-workspace/npmscripts/affected-apps.md index 5ed138ef57..2fd303e55b 100644 --- a/docs/react/api-workspace/npmscripts/affected-apps.md +++ b/docs/react/api-workspace/npmscripts/affected-apps.md @@ -40,6 +40,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -68,6 +72,10 @@ Isolate projects which previously failed Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/react/api-workspace/npmscripts/affected-build.md b/docs/react/api-workspace/npmscripts/affected-build.md index 8c00065fb8..fa98ccca64 100644 --- a/docs/react/api-workspace/npmscripts/affected-build.md +++ b/docs/react/api-workspace/npmscripts/affected-build.md @@ -58,6 +58,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -98,6 +102,10 @@ Parallelize the command Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/react/api-workspace/npmscripts/affected-dep-graph.md b/docs/react/api-workspace/npmscripts/affected-dep-graph.md index 1631164a5c..95b7d21807 100644 --- a/docs/react/api-workspace/npmscripts/affected-dep-graph.md +++ b/docs/react/api-workspace/npmscripts/affected-dep-graph.md @@ -46,6 +46,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -78,6 +82,10 @@ Isolate projects which previously failed Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/react/api-workspace/npmscripts/affected-e2e.md b/docs/react/api-workspace/npmscripts/affected-e2e.md index 1fc3577976..e2b3d59c31 100644 --- a/docs/react/api-workspace/npmscripts/affected-e2e.md +++ b/docs/react/api-workspace/npmscripts/affected-e2e.md @@ -58,6 +58,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -98,6 +102,10 @@ Parallelize the command Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/react/api-workspace/npmscripts/affected-libs.md b/docs/react/api-workspace/npmscripts/affected-libs.md index 379d1e1afa..6196a31aac 100644 --- a/docs/react/api-workspace/npmscripts/affected-libs.md +++ b/docs/react/api-workspace/npmscripts/affected-libs.md @@ -40,6 +40,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -68,6 +72,10 @@ Isolate projects which previously failed Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/react/api-workspace/npmscripts/affected-lint.md b/docs/react/api-workspace/npmscripts/affected-lint.md index da208ac511..b67cee8e03 100644 --- a/docs/react/api-workspace/npmscripts/affected-lint.md +++ b/docs/react/api-workspace/npmscripts/affected-lint.md @@ -58,6 +58,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -98,6 +102,10 @@ Parallelize the command Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/react/api-workspace/npmscripts/affected-test.md b/docs/react/api-workspace/npmscripts/affected-test.md index ffbb5b8c75..125b641c10 100644 --- a/docs/react/api-workspace/npmscripts/affected-test.md +++ b/docs/react/api-workspace/npmscripts/affected-test.md @@ -58,6 +58,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -98,6 +102,10 @@ Parallelize the command Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/react/api-workspace/npmscripts/affected.md b/docs/react/api-workspace/npmscripts/affected.md index d7bcc0ef70..e79e2a65a1 100644 --- a/docs/react/api-workspace/npmscripts/affected.md +++ b/docs/react/api-workspace/npmscripts/affected.md @@ -64,6 +64,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -104,6 +108,10 @@ Parallelize the command Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### target Task to run for affected projects diff --git a/docs/react/api-workspace/npmscripts/format-check.md b/docs/react/api-workspace/npmscripts/format-check.md index 8d7c53c5be..e0243eeb1b 100644 --- a/docs/react/api-workspace/npmscripts/format-check.md +++ b/docs/react/api-workspace/npmscripts/format-check.md @@ -22,6 +22,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -50,6 +54,10 @@ Isolate projects which previously failed Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/react/api-workspace/npmscripts/format-write.md b/docs/react/api-workspace/npmscripts/format-write.md index 4d75c4e5a2..c498cc2449 100644 --- a/docs/react/api-workspace/npmscripts/format-write.md +++ b/docs/react/api-workspace/npmscripts/format-write.md @@ -22,6 +22,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -50,6 +54,10 @@ Isolate projects which previously failed Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/web/api-workspace/npmscripts/affected-apps.md b/docs/web/api-workspace/npmscripts/affected-apps.md index 5ed138ef57..2fd303e55b 100644 --- a/docs/web/api-workspace/npmscripts/affected-apps.md +++ b/docs/web/api-workspace/npmscripts/affected-apps.md @@ -40,6 +40,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -68,6 +72,10 @@ Isolate projects which previously failed Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/web/api-workspace/npmscripts/affected-build.md b/docs/web/api-workspace/npmscripts/affected-build.md index 8c00065fb8..fa98ccca64 100644 --- a/docs/web/api-workspace/npmscripts/affected-build.md +++ b/docs/web/api-workspace/npmscripts/affected-build.md @@ -58,6 +58,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -98,6 +102,10 @@ Parallelize the command Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/web/api-workspace/npmscripts/affected-dep-graph.md b/docs/web/api-workspace/npmscripts/affected-dep-graph.md index 1631164a5c..95b7d21807 100644 --- a/docs/web/api-workspace/npmscripts/affected-dep-graph.md +++ b/docs/web/api-workspace/npmscripts/affected-dep-graph.md @@ -46,6 +46,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -78,6 +82,10 @@ Isolate projects which previously failed Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/web/api-workspace/npmscripts/affected-e2e.md b/docs/web/api-workspace/npmscripts/affected-e2e.md index 1fc3577976..e2b3d59c31 100644 --- a/docs/web/api-workspace/npmscripts/affected-e2e.md +++ b/docs/web/api-workspace/npmscripts/affected-e2e.md @@ -58,6 +58,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -98,6 +102,10 @@ Parallelize the command Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/web/api-workspace/npmscripts/affected-libs.md b/docs/web/api-workspace/npmscripts/affected-libs.md index 379d1e1afa..6196a31aac 100644 --- a/docs/web/api-workspace/npmscripts/affected-libs.md +++ b/docs/web/api-workspace/npmscripts/affected-libs.md @@ -40,6 +40,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -68,6 +72,10 @@ Isolate projects which previously failed Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/web/api-workspace/npmscripts/affected-lint.md b/docs/web/api-workspace/npmscripts/affected-lint.md index da208ac511..b67cee8e03 100644 --- a/docs/web/api-workspace/npmscripts/affected-lint.md +++ b/docs/web/api-workspace/npmscripts/affected-lint.md @@ -58,6 +58,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -98,6 +102,10 @@ Parallelize the command Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/web/api-workspace/npmscripts/affected-test.md b/docs/web/api-workspace/npmscripts/affected-test.md index ffbb5b8c75..125b641c10 100644 --- a/docs/web/api-workspace/npmscripts/affected-test.md +++ b/docs/web/api-workspace/npmscripts/affected-test.md @@ -58,6 +58,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -98,6 +102,10 @@ Parallelize the command Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/web/api-workspace/npmscripts/affected.md b/docs/web/api-workspace/npmscripts/affected.md index d7bcc0ef70..e79e2a65a1 100644 --- a/docs/web/api-workspace/npmscripts/affected.md +++ b/docs/web/api-workspace/npmscripts/affected.md @@ -64,6 +64,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -104,6 +108,10 @@ Parallelize the command Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### target Task to run for affected projects diff --git a/docs/web/api-workspace/npmscripts/format-check.md b/docs/web/api-workspace/npmscripts/format-check.md index 8d7c53c5be..e0243eeb1b 100644 --- a/docs/web/api-workspace/npmscripts/format-check.md +++ b/docs/web/api-workspace/npmscripts/format-check.md @@ -22,6 +22,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -50,6 +54,10 @@ Isolate projects which previously failed Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/docs/web/api-workspace/npmscripts/format-write.md b/docs/web/api-workspace/npmscripts/format-write.md index 4d75c4e5a2..c498cc2449 100644 --- a/docs/web/api-workspace/npmscripts/format-write.md +++ b/docs/web/api-workspace/npmscripts/format-write.md @@ -22,6 +22,10 @@ All projects Base of the current branch (usually master) +### configuration + +This is the configuration to use when performing tasks on projects + ### exclude Default: `` @@ -50,6 +54,10 @@ Isolate projects which previously failed Produces a plain output for affected:apps and affected:libs +### runner + +This is the name of the tasks runner configured in nx.json + ### uncommitted Uncommitted changes diff --git a/e2e/affected.test.ts b/e2e/affected.test.ts index 477521617a..a666b77c8c 100644 --- a/e2e/affected.test.ts +++ b/e2e/affected.test.ts @@ -143,13 +143,14 @@ forEachCli(() => { // affected:build should pass non-nx flags to the CLI const buildWithFlags = runCommand( - `npm run affected:build -- --files="libs/${mylib}/src/index.ts" --stats-json` + `npm run affected:build -- --files="libs/${mylib}/src/index.ts" -- --stats-json` ); expect(buildWithFlags).toContain(`Running target build for projects:`); expect(buildWithFlags).toContain(`- ${myapp}`); expect(buildWithFlags).toContain(`- ${mypublishablelib}`); - expect(buildWithFlags).toContain('With flags: --stats-json=true'); + expect(buildWithFlags).toContain('With flags:'); + expect(buildWithFlags).toContain('--stats-json=true'); if (supportUi()) { const e2e = runCommand( @@ -221,7 +222,8 @@ forEachCli(() => { const lintWithJsonFormating = runCommand( `npm run affected:lint -- --files="libs/${mylib}/src/index.ts" -- --format json` ); - expect(lintWithJsonFormating).toContain('With flags: --format json'); + expect(lintWithJsonFormating).toContain('With flags:'); + expect(lintWithJsonFormating).toContain('--format=json'); const unitTestsExcluded = runCommand( `npm run affected:test -- --files="libs/${mylib}/src/index.ts" --exclude=${myapp},${mypublishablelib}` diff --git a/packages/workspace/src/command-line/affected-apps.spec.ts b/packages/workspace/src/command-line/affected-apps.spec.ts index 24d1b8ad1d..18ef54b9be 100644 --- a/packages/workspace/src/command-line/affected-apps.spec.ts +++ b/packages/workspace/src/command-line/affected-apps.spec.ts @@ -3,11 +3,11 @@ import { getAffectedApps, getAffectedLibs, getAffectedProjects, - getAffectedProjectsWithTarget, + getAffectedProjectsWithTargetAndConfiguration, getAllApps, getAllLibs, getAllProjects, - getAllProjectsWithTarget + getAllProjectsWithTargetAndConfiguration } from './affected-apps'; import { DependencyType } from './deps-calculator'; @@ -23,7 +23,11 @@ describe('affected-apps', () => { architect: { lint: {}, test: {}, - build: {} + build: { + configurations: { + production: {} + } + } } }, app2: { @@ -196,11 +200,46 @@ describe('affected-apps', () => { }); }); - describe('getAffectedProjectsWithTarget', () => { + describe('getAffectedProjectsWithTargetAndConfiguration', () => { it('should get none if no projects are affected', () => { - expect(getAffectedProjectsWithTarget(affectedMetadata, 'test')).toEqual( - [] - ); + expect( + getAffectedProjectsWithTargetAndConfiguration(affectedMetadata, 'test') + ).toEqual([]); + }); + + it('should find affected projects that can be built', () => { + projectStates.lib1 = { + affected: true, + touched: true + }; + projectStates.app1.affected = true; + projectStates['app1-e2e'].affected = true; + projectStates.app2.affected = true; + projectStates['customName-e2e'].affected = true; + expect( + getAffectedProjectsWithTargetAndConfiguration(affectedMetadata, 'build') + ).toEqual([ + affectedMetadata.dependencyGraph.projects.app1, + affectedMetadata.dependencyGraph.projects.app2 + ]); + }); + + it('should find affected projects that can be built for production', () => { + projectStates.lib1 = { + affected: true, + touched: true + }; + projectStates.app1.affected = true; + projectStates['app1-e2e'].affected = true; + projectStates.app2.affected = true; + projectStates['customName-e2e'].affected = true; + expect( + getAffectedProjectsWithTargetAndConfiguration( + affectedMetadata, + 'build', + 'production' + ) + ).toEqual([affectedMetadata.dependencyGraph.projects.app1]); }); it('should find affected projects that can be linted', () => { @@ -212,12 +251,14 @@ describe('affected-apps', () => { projectStates['app1-e2e'].affected = true; projectStates.app2.affected = true; projectStates['customName-e2e'].affected = true; - expect(getAffectedProjectsWithTarget(affectedMetadata, 'lint')).toEqual([ - 'lib1', - 'app1', - 'app1-e2e', - 'app2', - 'customName-e2e' + expect( + getAffectedProjectsWithTargetAndConfiguration(affectedMetadata, 'lint') + ).toEqual([ + affectedMetadata.dependencyGraph.projects.lib1, + affectedMetadata.dependencyGraph.projects.app1, + affectedMetadata.dependencyGraph.projects['app1-e2e'], + affectedMetadata.dependencyGraph.projects.app2, + affectedMetadata.dependencyGraph.projects['customName-e2e'] ]); }); @@ -230,10 +271,12 @@ describe('affected-apps', () => { projectStates['app1-e2e'].affected = true; projectStates.app2.affected = true; projectStates['customName-e2e'].affected = true; - expect(getAffectedProjectsWithTarget(affectedMetadata, 'test')).toEqual([ - 'lib1', - 'app1', - 'app2' + expect( + getAffectedProjectsWithTargetAndConfiguration(affectedMetadata, 'test') + ).toEqual([ + affectedMetadata.dependencyGraph.projects.lib1, + affectedMetadata.dependencyGraph.projects.app1, + affectedMetadata.dependencyGraph.projects.app2 ]); }); @@ -246,9 +289,11 @@ describe('affected-apps', () => { projectStates['app1-e2e'].affected = true; projectStates.app2.affected = true; projectStates['customName-e2e'].affected = true; - expect(getAffectedProjectsWithTarget(affectedMetadata, 'e2e')).toEqual([ - 'app1-e2e', - 'customName-e2e' + expect( + getAffectedProjectsWithTargetAndConfiguration(affectedMetadata, 'e2e') + ).toEqual([ + affectedMetadata.dependencyGraph.projects['app1-e2e'], + affectedMetadata.dependencyGraph.projects['customName-e2e'] ]); }); }); @@ -280,36 +325,54 @@ describe('affected-apps', () => { describe('getAllProjectsWithTarget', () => { it('should get all projects that can be linted', () => { - expect(getAllProjectsWithTarget(affectedMetadata, 'lint')).toEqual([ - 'lib1', - 'app1', - 'app1-e2e', - 'lib2', - 'app2', - 'customName-e2e' + expect( + getAllProjectsWithTargetAndConfiguration(affectedMetadata, 'lint') + ).toEqual([ + affectedMetadata.dependencyGraph.projects.lib1, + affectedMetadata.dependencyGraph.projects.app1, + affectedMetadata.dependencyGraph.projects['app1-e2e'], + affectedMetadata.dependencyGraph.projects.lib2, + affectedMetadata.dependencyGraph.projects.app2, + affectedMetadata.dependencyGraph.projects['customName-e2e'] ]); }); it('should get all projects that can be tested', () => { - expect(getAllProjectsWithTarget(affectedMetadata, 'test')).toEqual([ - 'lib1', - 'app1', - 'lib2', - 'app2' + expect( + getAllProjectsWithTargetAndConfiguration(affectedMetadata, 'test') + ).toEqual([ + affectedMetadata.dependencyGraph.projects.lib1, + affectedMetadata.dependencyGraph.projects.app1, + affectedMetadata.dependencyGraph.projects.lib2, + affectedMetadata.dependencyGraph.projects.app2 ]); }); it('should get all projects that can be built', () => { - expect(getAllProjectsWithTarget(affectedMetadata, 'build')).toEqual([ - 'app1', - 'app2' + expect( + getAllProjectsWithTargetAndConfiguration(affectedMetadata, 'build') + ).toEqual([ + affectedMetadata.dependencyGraph.projects.app1, + affectedMetadata.dependencyGraph.projects.app2 ]); }); + it('should get all projects that can be built for production', () => { + expect( + getAllProjectsWithTargetAndConfiguration( + affectedMetadata, + 'build', + 'production' + ) + ).toEqual([affectedMetadata.dependencyGraph.projects.app1]); + }); + it('should get all projects that can be e2e-tested', () => { - expect(getAllProjectsWithTarget(affectedMetadata, 'e2e')).toEqual([ - 'app1-e2e', - 'customName-e2e' + expect( + getAllProjectsWithTargetAndConfiguration(affectedMetadata, 'e2e') + ).toEqual([ + affectedMetadata.dependencyGraph.projects['app1-e2e'], + affectedMetadata.dependencyGraph.projects['customName-e2e'] ]); }); }); diff --git a/packages/workspace/src/command-line/affected-apps.ts b/packages/workspace/src/command-line/affected-apps.ts index e79b3de631..a9ec46b835 100644 --- a/packages/workspace/src/command-line/affected-apps.ts +++ b/packages/workspace/src/command-line/affected-apps.ts @@ -26,16 +26,17 @@ export function getAffectedProjects( ).map(project => project.name); } -export function getAffectedProjectsWithTarget( +export function getAffectedProjectsWithTargetAndConfiguration( affectedMetadata: AffectedMetadata, - target: string -): string[] { + target: string, + configuration?: string +): ProjectNode[] { return filterAffectedMetadata( affectedMetadata, project => affectedMetadata.projectStates[project.name].affected && - project.architect[target] - ).map(project => project.name); + projectHasTargetAndConfiguration(project, target, configuration) + ); } export function getAllApps(affectedMetadata: AffectedMetadata): string[] { @@ -58,14 +59,33 @@ export function getAllProjects(affectedMetadata: AffectedMetadata): string[] { ); } -export function getAllProjectsWithTarget( +export function getAllProjectsWithTargetAndConfiguration( affectedMetadata: AffectedMetadata, - target: string -): string[] { - return filterAffectedMetadata( - affectedMetadata, - project => project.architect[target] - ).map(project => project.name); + target: string, + configuration?: string +): ProjectNode[] { + return filterAffectedMetadata(affectedMetadata, project => + projectHasTargetAndConfiguration(project, target, configuration) + ); +} + +export function projectHasTargetAndConfiguration( + project: ProjectNode, + target: string, + configuration?: string +) { + if (!project.architect[target]) { + return false; + } + + if (!configuration) { + return !!project.architect[target]; + } else { + return ( + project.architect[target].configurations && + project.architect[target].configurations[configuration] + ); + } } function filterAffectedMetadata( diff --git a/packages/workspace/src/command-line/affected.spec.ts b/packages/workspace/src/command-line/affected.spec.ts new file mode 100644 index 0000000000..cbb4a2e222 --- /dev/null +++ b/packages/workspace/src/command-line/affected.spec.ts @@ -0,0 +1,153 @@ +import { processArgs } from './affected'; +import { NxJson } from './shared'; +import defaultTasksRunner from '../tasks-runner/default-tasks-runner'; +import { TasksRunner } from '../tasks-runner/tasks-runner'; + +describe('processArgs', () => { + let nxJson: NxJson; + let mockRunner: TasksRunner; + + beforeEach(() => { + nxJson = { + npmScope: 'proj', + projects: {} + }; + mockRunner = jest.fn(); + }); + + it('should process nx specific arguments as affected args', () => { + expect( + processArgs( + { + files: [''], + notNxArg: true, + _: ['--override'], + $0: '' + }, + nxJson + ).affectedArgs + ).toEqual({ + files: [''] + }); + }); + + it('should process non nx specific arguments as tasks runner args', () => { + expect( + processArgs( + { + files: [''], + notNxArg: true, + _: ['--override'], + $0: '' + }, + nxJson + ).tasksRunnerOptions + ).toEqual({ + notNxArg: true + }); + }); + + it('should process delimited args as task overrides', () => { + expect( + processArgs( + { + files: [''], + notNxArg: true, + _: ['', '--override'], + $0: '' + }, + nxJson + ).taskOverrides + ).toEqual({ + override: true + }); + }); + + it('should get a default tasks runner', () => { + expect( + processArgs( + { + files: [''], + notNxArg: true, + _: ['', '--override'], + $0: '' + }, + nxJson + ).tasksRunner + ).toEqual(defaultTasksRunner); + }); + + it('should get a custom tasks runner', () => { + jest.mock('custom-runner', () => mockRunner, { + virtual: true + }); + nxJson.tasksRunnerOptions = { + custom: { + runner: 'custom-runner' + } + }; + expect( + processArgs( + { + files: [''], + notNxArg: true, + runner: 'custom', + _: ['', '--override'], + $0: '' + }, + nxJson + ).tasksRunner + ).toEqual(mockRunner); + }); + + it('should get a custom tasks runner with options', () => { + jest.mock('custom-runner', () => mockRunner, { + virtual: true + }); + nxJson.tasksRunnerOptions = { + custom: { + runner: 'custom-runner', + options: { + runnerOption: 'runner-option' + } + } + }; + expect( + processArgs( + { + files: [''], + notNxArg: true, + runner: 'custom', + _: ['', '--override'], + $0: '' + }, + nxJson + ).tasksRunnerOptions + ).toEqual({ + runnerOption: 'runner-option', + notNxArg: true + }); + }); + + it('should get a custom defined default tasks runner', () => { + jest.mock('custom-default-runner', () => mockRunner, { + virtual: true + }); + nxJson.tasksRunnerOptions = { + default: { + runner: 'custom-default-runner' + } + }; + expect( + processArgs( + { + files: [''], + notNxArg: true, + _: ['', '--override'], + $0: '' + }, + nxJson + ).tasksRunner + ).toEqual(mockRunner); + }); +}); diff --git a/packages/workspace/src/command-line/affected.ts b/packages/workspace/src/command-line/affected.ts index 110f39f30a..e9d696620b 100644 --- a/packages/workspace/src/command-line/affected.ts +++ b/packages/workspace/src/command-line/affected.ts @@ -1,28 +1,39 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as runAll from 'npm-run-all'; import * as yargs from 'yargs'; +import * as yargsParser from 'yargs-parser'; +import { join } from 'path'; import { parseFiles, - readWorkspaceJson, printArgsWarning, - cliCommand, - getAffectedMetadata + getAffectedMetadata, + AffectedMetadata, + readNxJson, + ProjectNode, + NxJson } from './shared'; import { getAffectedApps, getAffectedLibs, getAffectedProjects, - getAffectedProjectsWithTarget, + getAffectedProjectsWithTargetAndConfiguration, getAllApps, getAllLibs, getAllProjects, - getAllProjectsWithTarget + getAllProjectsWithTargetAndConfiguration, + projectHasTargetAndConfiguration } from './affected-apps'; import { generateGraph } from './dep-graph'; import { WorkspaceResults } from './workspace-results'; import { output } from './output'; +import { + AffectedEventType, + Task, + TaskCompleteEvent, + TasksRunner +} from '../tasks-runner/tasks-runner'; +import { appRootPath } from '../utils/app-root'; +import { defaultTasksRunner } from '../tasks-runner/default-tasks-runner'; +import { isRelativePath } from '../utils/fileutils'; export interface YargsAffectedOptions extends yargs.Arguments, @@ -30,6 +41,8 @@ export interface YargsAffectedOptions export interface AffectedOptions { target?: string; + configuration?: string; + runner?: string; parallel?: boolean; maxParallel?: number; untracked?: boolean; @@ -49,14 +62,15 @@ export interface AffectedOptions { plain?: boolean; } -const commonCommands = ['build', 'test', 'lint', 'e2e']; +interface ProcessedArgs { + affectedArgs: YargsAffectedOptions; + taskOverrides: any; + tasksRunnerOptions: any; + tasksRunner: TasksRunner; +} export function affected(parsedArgs: YargsAffectedOptions): void { const target = parsedArgs.target; - const rest: string[] = [ - ...parsedArgs._.slice(1), - ...filterNxSpecificArgs(parsedArgs) - ]; const workspaceResults = new WorkspaceResults(target); @@ -70,10 +84,10 @@ export function affected(parsedArgs: YargsAffectedOptions): void { ? getAllApps(affectedMetadata) : getAffectedApps(affectedMetadata) ) - .filter(app => !parsedArgs.exclude.includes(app)) + .filter(affectedApp => !parsedArgs.exclude.includes(affectedApp)) .filter( - project => - !parsedArgs.onlyFailed || !workspaceResults.getResult(project) + affectedApp => + !parsedArgs.onlyFailed || !workspaceResults.getResult(affectedApp) ); if (parsedArgs.plain) { console.log(apps.join(' ')); @@ -92,10 +106,10 @@ export function affected(parsedArgs: YargsAffectedOptions): void { ? getAllLibs(affectedMetadata) : getAffectedLibs(affectedMetadata) ) - .filter(app => !parsedArgs.exclude.includes(app)) + .filter(affectedLib => !parsedArgs.exclude.includes(affectedLib)) .filter( - project => - !parsedArgs.onlyFailed || !workspaceResults.getResult(project) + affectedLib => + !parsedArgs.onlyFailed || !workspaceResults.getResult(affectedLib) ); if (parsedArgs.plain) { @@ -125,17 +139,28 @@ export function affected(parsedArgs: YargsAffectedOptions): void { break; } default: { + const nxJson = readNxJson(); + const processedArgs = processArgs(parsedArgs, nxJson); const projects = (parsedArgs.all - ? getAllProjectsWithTarget(affectedMetadata, target) - : getAffectedProjectsWithTarget(affectedMetadata, target) + ? getAllProjectsWithTargetAndConfiguration( + affectedMetadata, + target, + processedArgs.affectedArgs.configuration + ) + : getAffectedProjectsWithTargetAndConfiguration( + affectedMetadata, + target, + processedArgs.affectedArgs.configuration + ) ) - .filter(project => !parsedArgs.exclude.includes(project)) + .filter(project => !parsedArgs.exclude.includes(project.name)) .filter( project => - !parsedArgs.onlyFailed || !workspaceResults.getResult(project) + !parsedArgs.onlyFailed || + !workspaceResults.getResult(project.name) ); printArgsWarning(parsedArgs); - runCommand(target, projects, parsedArgs, rest, workspaceResults); + runCommand(projects, affectedMetadata, processedArgs, workspaceResults); break; } } @@ -158,145 +183,202 @@ function printError(e: any, verbose?: boolean) { } async function runCommand( - targetName: string, - projects: string[], - parsedArgs: YargsAffectedOptions, - args: string[], + affectedProjects: ProjectNode[], + affectedMetadata: AffectedMetadata, + processedArgs: ProcessedArgs, workspaceResults: WorkspaceResults ) { - if (projects.length <= 0) { - output.logSingleLine( - `No affected projects to run target "${targetName}" on` - ); + const { + affectedArgs, + taskOverrides, + tasksRunnerOptions, + tasksRunner + } = processedArgs; + + if (affectedProjects.length <= 0) { + let description = `with "${affectedArgs.target}"`; + if (affectedArgs.configuration) { + description += ` that are configured for "${affectedArgs.configuration}"`; + } + output.logSingleLine(`No projects ${description} were affected`); return; } - const cli = cliCommand(); - - const bodyLines = projects.map( - project => `${output.colors.gray('-')} ${project}` + const bodyLines = affectedProjects.map( + affectedProject => `${output.colors.gray('-')} ${affectedProject.name}` ); - if (args.length > 0) { + if (Object.keys(taskOverrides).length > 0) { bodyLines.push(''); - bodyLines.push( - `${output.colors.gray('With flags:')} ${output.bold(args.join(' '))}` - ); + bodyLines.push(`${output.colors.gray('With flags:')}`); + Object.entries(taskOverrides) + .map(([flag, value]) => ` --${flag}=${value}`) + .forEach(arg => bodyLines.push(arg)); } output.log({ - title: `${output.colors.gray( - 'Running target' - )} ${targetName} ${output.colors.gray('for projects:')}`, + title: `${output.colors.gray('Running target')} ${ + affectedArgs.target + } ${output.colors.gray('for projects:')}`, bodyLines }); output.addVerticalSeparator(); - const workspaceJson = readWorkspaceJson(); - const projectMetadata = new Map(); - projects.forEach(project => { - projectMetadata.set(project, workspaceJson.projects[project]); - }); - - // Make sure the `package.json` has the `nx: "nx"` command needed by `npm-run-all` - const packageJson = JSON.parse( - fs.readFileSync('./package.json').toString('utf-8') - ); - if (!packageJson.scripts || !packageJson.scripts[cli]) { - output.error({ - title: `The "scripts" section of your 'package.json' must contain "${cli}": "${cli}"`, - bodyLines: [ - output.colors.gray('...'), - ' "scripts": {', - output.colors.gray(' ...'), - ` "${cli}": "${cli}"`, - output.colors.gray(' ...'), - ' }', - output.colors.gray('...') - ] - }); - return process.exit(1); - } - - try { - const isYarn = path - .basename(process.env.npm_execpath || 'npm') - .startsWith('yarn'); - await runAll( - projects.map(proj => { - return commonCommands.includes(targetName) - ? `${cli} ${isYarn ? '' : '--'} ${targetName} ${proj} ${transformArgs( - args, - proj, - projectMetadata.get(proj) - ).join(' ')} ` - : `${cli} ${ - isYarn ? '' : '--' - } run ${proj}:${targetName} ${transformArgs( - args, - proj, - projectMetadata.get(proj) - ).join(' ')} `; - }), - { - parallel: parsedArgs.parallel || false, - maxParallel: parsedArgs.maxParallel || 1, - continueOnError: true, - stdin: process.stdin, - stdout: process.stdout, - stderr: process.stderr - } - ); - - projects.forEach(project => { - workspaceResults.success(project); - }); - } catch (e) { - e.results.forEach((result, i) => { - if (result.code === 0) { - workspaceResults.success(projects[i]); - } else { - workspaceResults.fail(projects[i]); - } - }); - } finally { - // fix for https://github.com/nrwl/nx/issues/1666 - if (process.stdin['unref']) (process.stdin as any).unref(); - } - - workspaceResults.saveResults(); - workspaceResults.printResults( - parsedArgs.onlyFailed, - `Running target "${targetName}" for affected projects succeeded`, - `Running target "${targetName}" for affected projects failed` + const tasks: Task[] = affectedProjects.map(affectedProject => + createTask({ + project: affectedProject, + target: processedArgs.affectedArgs.target, + configuration: processedArgs.affectedArgs.configuration, + overrides: processedArgs.taskOverrides + }) ); - if (workspaceResults.hasFailure) { - process.exit(1); - } -} - -function transformArgs( - args: string[], - projectName: string, - projectMetadata: any -) { - return args.map(arg => { - const regex = /{project\.([^}]+)}/g; - return arg.replace(regex, (_, group: string) => { - if (group.includes('.')) { - throw new Error('Only top-level properties can be interpolated'); + const tasksMap: { + [projectName: string]: { [targetName: string]: Task }; + } = {}; + Object.entries(affectedMetadata.dependencyGraph.projects).forEach( + ([projectName, project]) => { + if ( + projectHasTargetAndConfiguration( + project, + processedArgs.affectedArgs.target, + processedArgs.affectedArgs.configuration + ) + ) { + tasksMap[projectName] = { + [processedArgs.affectedArgs.target]: createTask({ + project: project, + target: processedArgs.affectedArgs.target, + configuration: processedArgs.affectedArgs.configuration, + overrides: processedArgs.taskOverrides + }) + }; } + } + ); - if (group === 'name') { - return projectName; + tasksRunner(tasks, tasksRunnerOptions, { + dependencyGraph: affectedMetadata.dependencyGraph, + tasksMap + }).subscribe({ + next: (event: TaskCompleteEvent) => { + switch (event.type) { + case AffectedEventType.TaskComplete: { + workspaceResults.setResult(event.task.target.project, event.success); + } } - return projectMetadata[group]; - }); + }, + error: console.error, + complete: () => { + // fix for https://github.com/nrwl/nx/issues/1666 + if (process.stdin['unref']) (process.stdin as any).unref(); + + workspaceResults.saveResults(); + workspaceResults.printResults( + affectedArgs.onlyFailed, + `Running target "${affectedArgs.target}" for affected projects succeeded`, + `Running target "${affectedArgs.target}" for affected projects failed` + ); + + if (workspaceResults.hasFailure) { + process.exit(1); + } + } }); } -function filterNxSpecificArgs(parsedArgs: YargsAffectedOptions): string[] { +function createTask({ + project, + target, + configuration, + overrides +}: { + project: ProjectNode; + target: string; + configuration: string; + overrides: Object; +}): Task { + return { + id: getTaskId({ + project: project.name, + target: target, + configuration: configuration + }), + target: { + project: project.name, + target, + configuration + }, + overrides: interpolateOverrides(overrides, project.name, project) + }; +} + +function getTaskId({ + project, + target, + configuration +}: { + project: string; + target: string; + configuration?: string; +}): string { + let id = project + ':' + target; + if (configuration) { + id += ':' + configuration; + } + return id; +} + +function getTasksRunner( + runner: string | undefined, + nxJson: NxJson +): { + tasksRunner: TasksRunner; + options: unknown; +} { + if (!nxJson.tasksRunnerOptions) { + return { + tasksRunner: defaultTasksRunner, + options: {} + }; + } + + if (!runner && !nxJson.tasksRunnerOptions.default) { + return { + tasksRunner: defaultTasksRunner, + options: {} + }; + } + + runner = runner || 'default'; + + if (nxJson.tasksRunnerOptions[runner]) { + let modulePath: string = nxJson.tasksRunnerOptions[runner].runner; + if (isRelativePath(modulePath)) { + modulePath = join(appRootPath, modulePath); + } + return { + tasksRunner: require(modulePath), + options: nxJson.tasksRunnerOptions[runner].options + }; + } else { + throw new Error(`Could not find runner configuration for ${runner}`); + } +} + +function getNxSpecificOptions( + parsedArgs: YargsAffectedOptions +): YargsAffectedOptions { + const filteredArgs = {}; + nxSpecificFlags.forEach(flag => { + filteredArgs[flag] = parsedArgs[flag]; + }); + return filteredArgs as YargsAffectedOptions; +} + +function getNonNxSpecificOptions( + parsedArgs: YargsAffectedOptions +): YargsAffectedOptions { const filteredArgs = { ...parsedArgs }; // Delete Nx arguments from parsed Args nxSpecificFlags.forEach(flag => { @@ -308,18 +390,54 @@ function filterNxSpecificArgs(parsedArgs: YargsAffectedOptions): string[] { // Also remove the node path delete filteredArgs.$0; - // Re-serialize into a list of args - return Object.keys(filteredArgs).map(filteredArg => { - if (!Array.isArray(filteredArgs[filteredArg])) { - filteredArgs[filteredArg] = [filteredArgs[filteredArg]]; - } + return filteredArgs; +} - return filteredArgs[filteredArg] - .map(value => { - return `--${filteredArg}=${value}`; - }) - .join(' '); +export function processArgs( + parsedArgs: YargsAffectedOptions, + nxJson: NxJson +): ProcessedArgs { + const affectedArgs = getNxSpecificOptions(parsedArgs); + const { tasksRunner, options: configOptions } = getTasksRunner( + affectedArgs.runner, + nxJson + ); + const tasksRunnerOptions = { + ...configOptions, + ...getNonNxSpecificOptions(parsedArgs) + }; + const taskOverrides = yargsParser(parsedArgs._.slice(1)); + delete taskOverrides._; + return { + affectedArgs, + taskOverrides, + tasksRunner, + tasksRunnerOptions: tasksRunnerOptions + }; +} + +function interpolateOverrides( + args: T, + projectName: string, + projectMetadata: any +): T { + const interpolatedArgs: T = { ...args }; + Object.entries(interpolatedArgs).forEach(([name, value]) => { + if (typeof value === 'string') { + const regex = /{project\.([^}]+)}/g; + interpolatedArgs[name] = value.replace(regex, (_, group: string) => { + if (group.includes('.')) { + throw new Error('Only top-level properties can be interpolated'); + } + + if (group === 'name') { + return projectName; + } + return projectMetadata[group]; + }); + } }); + return interpolatedArgs; } /** @@ -329,13 +447,12 @@ function filterNxSpecificArgs(parsedArgs: YargsAffectedOptions): string[] { */ const dummyOptions: AffectedOptions = { target: '', - parallel: false, - maxParallel: 3, - 'max-parallel': false, + configuration: '', onlyFailed: false, 'only-failed': false, untracked: false, uncommitted: false, + runner: '', help: false, version: false, quiet: false, diff --git a/packages/workspace/src/command-line/nx-commands.ts b/packages/workspace/src/command-line/nx-commands.ts index 61f4ba474c..d61b1eeb56 100644 --- a/packages/workspace/src/command-line/nx-commands.ts +++ b/packages/workspace/src/command-line/nx-commands.ts @@ -254,6 +254,15 @@ function withAffectedOptions(yargs: yargs.Argv): yargs.Argv { coerce: parseCSV, default: [] }) + .options('runner', { + describe: 'This is the name of the tasks runner configured in nx.json', + type: 'string' + }) + .options('configuration', { + describe: + 'This is the configuration to use when performing tasks on projects', + type: 'string' + }) .options('only-failed', { describe: 'Isolate projects which previously failed', type: 'boolean', diff --git a/packages/workspace/src/command-line/shared.ts b/packages/workspace/src/command-line/shared.ts index 7ce99eab26..024ce0ac1d 100644 --- a/packages/workspace/src/command-line/shared.ts +++ b/packages/workspace/src/command-line/shared.ts @@ -24,6 +24,12 @@ export interface NxJson { projects: { [projectName: string]: NxJsonProjectConfig; }; + tasksRunnerOptions?: { + [tasksRunnerName: string]: { + runner: string; + options?: unknown; + }; + }; } export interface NxJsonProjectConfig { @@ -443,6 +449,7 @@ export function createAffectedMetadata( ): AffectedMetadata { const projectStates: ProjectStates = {}; const projects: ProjectMap = {}; + projectNodes.forEach(project => { projectStates[project.name] = { touched: false, diff --git a/packages/workspace/src/command-line/workspace-results.spec.ts b/packages/workspace/src/command-line/workspace-results.spec.ts index 0162966dbc..a0d3f634c5 100644 --- a/packages/workspace/src/command-line/workspace-results.spec.ts +++ b/packages/workspace/src/command-line/workspace-results.spec.ts @@ -22,7 +22,7 @@ describe('WorkspacesResults', () => { describe('success', () => { it('should return true when getting results', () => { - results.success('proj'); + results.setResult('proj', true); expect(results.getResult('proj')).toBe(true); }); @@ -32,7 +32,7 @@ describe('WorkspacesResults', () => { spyOn(fs, 'unlinkSync'); spyOn(fs, 'existsSync').and.returnValue(true); - results.success('proj'); + results.setResult('proj', true); results.saveResults(); expect(fs.writeSync).not.toHaveBeenCalled(); @@ -41,7 +41,7 @@ describe('WorkspacesResults', () => { it('should print results', () => { const projectName = 'proj'; - results.success(projectName); + results.setResult(projectName, true); spyOn(output, 'success'); const successTitle = 'Success'; @@ -60,7 +60,7 @@ describe('WorkspacesResults', () => { spyOn(output, 'success'); spyOn(output, 'warn'); - results.success(projectName); + results.setResult(projectName, true); const successTitle = 'Success'; @@ -85,7 +85,7 @@ describe('WorkspacesResults', () => { describe('fail', () => { it('should return false when getting results', () => { - results.fail('proj'); + results.setResult('proj', false); expect(results.getResult('proj')).toBe(false); }); @@ -93,7 +93,7 @@ describe('WorkspacesResults', () => { it('should save results to file system', () => { spyOn(fs, 'writeFileSync'); - results.fail('proj'); + results.setResult('proj', false); results.saveResults(); expect(fs.writeFileSync).toHaveBeenCalledWith( @@ -109,7 +109,7 @@ describe('WorkspacesResults', () => { it('should print results', () => { const projectName = 'proj'; - results.fail(projectName); + results.setResult(projectName, false); spyOn(output, 'error'); const errorTitle = 'Fail'; @@ -128,7 +128,7 @@ describe('WorkspacesResults', () => { it('should tell the user that they can isolate only the failed tests', () => { const projectName = 'proj'; - results.fail(projectName); + results.setResult(projectName, false); spyOn(output, 'error'); const errorTitle = 'Fail'; diff --git a/packages/workspace/src/command-line/workspace-results.ts b/packages/workspace/src/command-line/workspace-results.ts index 3045a0147c..c94ec2545d 100644 --- a/packages/workspace/src/command-line/workspace-results.ts +++ b/packages/workspace/src/command-line/workspace-results.ts @@ -51,14 +51,6 @@ export class WorkspaceResults { return this.commandResults.results[projectName]; } - fail(projectName: string) { - this.setResult(projectName, false); - } - - success(projectName: string) { - this.setResult(projectName, true); - } - saveResults() { if (Object.values(this.commandResults.results).includes(false)) { writeJsonFile(RESULTS_FILE, this.commandResults); @@ -120,7 +112,7 @@ export class WorkspaceResults { }); } - private setResult(projectName: string, result: boolean) { + setResult(projectName: string, result: boolean) { this.commandResults.results[projectName] = result; } } diff --git a/packages/workspace/src/tasks-runner/default-tasks-runner.spec.ts b/packages/workspace/src/tasks-runner/default-tasks-runner.spec.ts new file mode 100644 index 0000000000..47b55c8b31 --- /dev/null +++ b/packages/workspace/src/tasks-runner/default-tasks-runner.spec.ts @@ -0,0 +1,189 @@ +import defaultTasksRunner from './default-tasks-runner'; +import { AffectedEventType, Task } from './tasks-runner'; +jest.mock('npm-run-all', () => jest.fn()); +import * as runAll from 'npm-run-all'; +jest.mock('../command-line/shared', () => ({ + cliCommand: () => 'nx' +})); +jest.mock('../utils/fileutils', () => ({ + readJsonFile: () => ({ + scripts: { + nx: 'nx' + } + }) +})); + +describe('defaultTasksRunner', () => { + let tasks: Task[]; + beforeEach(() => { + tasks = [ + { + id: 'task-1', + target: { + project: 'app-1', + target: 'target' + }, + overrides: {} + }, + { + id: 'task-2', + target: { + project: 'app-2', + target: 'target' + }, + overrides: {} + } + ]; + }); + + it('should run the correct commands through "npm-run-all"', done => { + runAll.mockImplementation(() => Promise.resolve()); + defaultTasksRunner(tasks, {}).subscribe({ + complete: () => { + expect(runAll).toHaveBeenCalledWith( + ['nx run app-1:target', 'nx run app-2:target'], + jasmine.anything() + ); + done(); + } + }); + }); + + it('should run the correct commands through "npm-run-all" when tasks have a configuration', done => { + runAll.mockImplementation(() => Promise.resolve()); + tasks = tasks.map(task => { + task.target.configuration = 'production'; + return task; + }); + defaultTasksRunner(tasks, {}).subscribe({ + complete: () => { + expect(runAll).toHaveBeenCalledWith( + ['nx run app-1:target:production', 'nx run app-2:target:production'], + jasmine.anything() + ); + done(); + } + }); + }); + + it('should run the correct commands through "npm-run-all" when tasks have overrides', done => { + runAll.mockImplementation(() => Promise.resolve()); + tasks = tasks.map(task => { + task.overrides = { + override: 'override-value' + }; + return task; + }); + defaultTasksRunner(tasks, {}).subscribe({ + complete: () => { + expect(runAll).toHaveBeenCalledWith( + [ + 'nx run app-1:target --override=override-value', + 'nx run app-2:target --override=override-value' + ], + jasmine.anything() + ); + done(); + } + }); + }); + + it('should run the correct commands through "npm-run-all" when tasks have configurations and overrides', done => { + runAll.mockImplementation(() => Promise.resolve()); + tasks = tasks.map(task => { + task.target.configuration = 'production'; + task.overrides = { + override: 'override-value' + }; + return task; + }); + defaultTasksRunner(tasks, {}).subscribe({ + complete: () => { + expect(runAll).toHaveBeenCalledWith( + [ + 'nx run app-1:target:production --override=override-value', + 'nx run app-2:target:production --override=override-value' + ], + jasmine.anything() + ); + done(); + } + }); + }); + + it('should pass the right options when options are passed', done => { + runAll.mockImplementation(() => Promise.resolve()); + defaultTasksRunner(tasks, { + parallel: true, + maxParallel: 5 + }).subscribe({ + complete: () => { + expect(runAll).toHaveBeenCalledWith( + jasmine.any(Array), + jasmine.objectContaining({ + parallel: true, + maxParallel: 5 + }) + ); + done(); + } + }); + }); + + it('should run emit task complete events when "run-all-prerender" resolves', done => { + runAll.mockImplementation(() => Promise.resolve()); + let i = 0; + const expected = [ + { + task: tasks[0], + type: AffectedEventType.TaskComplete, + success: true + }, + { + task: tasks[1], + type: AffectedEventType.TaskComplete, + success: true + } + ]; + defaultTasksRunner(tasks, {}).subscribe({ + next: event => { + expect(event).toEqual(expected[i++]); + }, + complete: done + }); + }); + + it('should run emit task complete events when "run-all-prerender" rejects', done => { + runAll.mockImplementation(() => + Promise.reject({ + results: [ + { + code: 0 + }, + { + code: 1 + } + ] + }) + ); + let i = 0; + const expected = [ + { + task: tasks[0], + type: AffectedEventType.TaskComplete, + success: true + }, + { + task: tasks[1], + type: AffectedEventType.TaskComplete, + success: false + } + ]; + defaultTasksRunner(tasks, {}).subscribe({ + next: event => { + expect(event).toEqual(expected[i++]); + }, + complete: done + }); + }); +}); diff --git a/packages/workspace/src/tasks-runner/default-tasks-runner.ts b/packages/workspace/src/tasks-runner/default-tasks-runner.ts new file mode 100644 index 0000000000..fc36b03599 --- /dev/null +++ b/packages/workspace/src/tasks-runner/default-tasks-runner.ts @@ -0,0 +1,121 @@ +import * as runAll from 'npm-run-all'; +import { Observable } from 'rxjs'; +import { basename } from 'path'; +import { + AffectedEventType, + Task, + TaskCompleteEvent, + TasksRunner +} from './tasks-runner'; +import { cliCommand } from '../command-line/shared'; +import { output } from '../command-line/output'; +import { readJsonFile } from '../utils/fileutils'; + +const commonCommands = ['build', 'test', 'lint', 'e2e', 'deploy']; + +export interface DefaultTasksRunnerOptions { + parallel?: boolean; + maxParallel?: number; +} + +export const defaultTasksRunner: TasksRunner = ( + tasks: Task[], + options: DefaultTasksRunnerOptions +): Observable => { + const additionalTaskOverrides = getLegacyTaskOverrides(options); + tasks.forEach(task => { + task.overrides = { + ...task.overrides, + ...additionalTaskOverrides + }; + }); + const commands = getCommands(tasks); + return new Observable(subscriber => { + runAll(commands, { + parallel: options.parallel || false, + maxParallel: options.maxParallel || 3, + continueOnError: true, + stdin: process.stdin, + stdout: process.stdout, + stderr: process.stderr + }) + .then(() => { + tasks.forEach(task => { + subscriber.next({ + task: task, + type: AffectedEventType.TaskComplete, + success: true + }); + }); + }) + .catch(e => { + e.results.forEach((result, i) => { + subscriber.next({ + task: tasks[i], + type: AffectedEventType.TaskComplete, + success: result.code === 0 + }); + }); + }) + .finally(() => { + subscriber.complete(); + // fix for https://github.com/nrwl/nx/issues/1666 + if (process.stdin['unref']) (process.stdin as any).unref(); + }); + }); +}; + +export default defaultTasksRunner; + +function getLegacyTaskOverrides(options: any) { + const legacyTaskOverrides = { ...options }; + delete legacyTaskOverrides.maxParallel; + delete legacyTaskOverrides['max-parallel']; + delete legacyTaskOverrides.parallel; + delete legacyTaskOverrides.verbose; + return legacyTaskOverrides; +} + +function getCommands(tasks: Task[]) { + const cli = cliCommand(); + assertPackageJsonScriptExists(cli); + const isYarn = basename(process.env.npm_execpath || 'npm').startsWith('yarn'); + return tasks.map(task => { + const args = Object.entries(task.overrides) + .map(([prop, value]) => `--${prop}=${value}`) + .join(' '); + return commonCommands.includes(task.target.target) + ? `${cli}${isYarn ? '' : ' --'} ${task.target.target} ${ + task.target.project + } ${ + task.target.configuration + ? `--configuration ${task.target.configuration} ` + : '' + }${args}` + : `${cli}${isYarn ? '' : ' --'} run ${task.target.project}:${ + task.target.target + }${task.target.configuration ? `:${task.target.configuration}` : ''}${ + args ? ' ' + args : '' + }`; + }); +} + +function assertPackageJsonScriptExists(cli: string) { + // Make sure the `package.json` has the `nx: "nx"` command needed by `npm-run-all` + const packageJson = readJsonFile('./package.json'); + if (!packageJson.scripts || !packageJson.scripts[cli]) { + output.error({ + title: `The "scripts" section of your 'package.json' must contain "${cli}": "${cli}"`, + bodyLines: [ + output.colors.gray('...'), + ' "scripts": {', + output.colors.gray(' ...'), + ` "${cli}": "${cli}"`, + output.colors.gray(' ...'), + ' }', + output.colors.gray('...') + ] + }); + return process.exit(1); + } +} diff --git a/packages/workspace/src/tasks-runner/tasks-runner.ts b/packages/workspace/src/tasks-runner/tasks-runner.ts new file mode 100644 index 0000000000..cc2a70f699 --- /dev/null +++ b/packages/workspace/src/tasks-runner/tasks-runner.ts @@ -0,0 +1,37 @@ +import { Observable } from 'rxjs'; +import { Target } from '@angular-devkit/architect'; + +import { DependencyGraph } from '../command-line/shared'; + +export interface Task { + id: string; + target: Target; + overrides: Object; +} + +export enum AffectedEventType { + TaskComplete = '[Task] Complete' +} + +export interface AffectedEvent { + task: Task; + type: AffectedEventType; +} + +export interface TaskCompleteEvent extends AffectedEvent { + type: AffectedEventType.TaskComplete; + success: boolean; +} + +export type TasksRunner = ( + tasks: Task[], + options?: T, + context?: { + dependencyGraph: DependencyGraph; + tasksMap: { + [projectName: string]: { + [targetName: string]: Task; + }; + }; + } +) => Observable; diff --git a/packages/workspace/src/utils/fileutils.ts b/packages/workspace/src/utils/fileutils.ts index 1405f481f3..ac5d832e9c 100644 --- a/packages/workspace/src/utils/fileutils.ts +++ b/packages/workspace/src/utils/fileutils.ts @@ -123,3 +123,7 @@ export function renameSync( cb(e); } } + +export function isRelativePath(path: string): boolean { + return path.startsWith('./') || path.startsWith('../'); +}