diff --git a/.circleci/config.yml b/.circleci/config.yml index a1697dc8f5..8d37dacce8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -204,9 +204,7 @@ jobs: mainmacos: executor: macos environment: - NX_CLOUD_DISTRIBUTED_EXECUTION: 'true' NX_E2E_CI_CACHE_KEY: e2e-circleci-macos - NX_CLOUD_DISTRIBUTED_EXECUTION_AGENT_COUNT: 2 steps: - run: name: Set dynamic nx run variable @@ -249,14 +247,6 @@ workflows: name: 'agent7' - agent: name: 'agent8' - - agent: - name: 'agent9' - pm: 'npm' - os: 'macos' - - agent: - name: 'agent10' - pm: 'npm' - os: 'macos' - main-linux - mainmacos: name: main-macos-e2e diff --git a/docs/shared/node-tutorial/06-computation-caching.md b/docs/shared/node-tutorial/06-computation-caching.md index 55aa4e6f5e..5707e67af1 100644 --- a/docs/shared/node-tutorial/06-computation-caching.md +++ b/docs/shared/node-tutorial/06-computation-caching.md @@ -33,45 +33,6 @@ chunk {main} main.js, main.js.map (main) 4.17 KiB [entry] [rendered] Based on the state of the source code and the environment, Nx was able to figure out that it had already run this exact command. Nx found the artifact in the local cache and replayed the output and restored the necessary files. -## --with-deps - -Nx is smart, so it knows how applications and libraries in the workspace depend on each other. - -**Run `nx lint todos --with-deps`, and you see that Nx lints both the `todos` app and the libraries it depends on.** - -```bash -> NX Running target lint for project todos and its 2 deps. - -——————————————————————————————————————————————— - -> nx run data:lint - -Linting "data"... - -All files pass linting. - - -> nx run auth:lint - -Linting "auth"... - -All files pass linting. - - -> nx run todos:lint - -Linting "todos"... - -All files pass linting. - - -——————————————————————————————————————————————— - -> NX SUCCESS Running target "lint" succeeded -``` - -> Add --parallel to any command, and Nx does most of the work in parallel. - ## What's Next - Continue to [Step 7: Test affected projects](/node-tutorial/07-test-affected-projects) diff --git a/e2e/nx-misc/src/extras.test.ts b/e2e/nx-misc/src/extras.test.ts index e386c255c2..51dd7bec7e 100644 --- a/e2e/nx-misc/src/extras.test.ts +++ b/e2e/nx-misc/src/extras.test.ts @@ -188,7 +188,7 @@ describe('Extra Nx Misc Tests', () => { expect(resultArgs).toContain('camel: d'); }, 120000); - it('should fail when a process exits non-zero', () => { + it('ttt should fail when a process exits non-zero', () => { updateProjectConfig(mylib, (config) => { config.targets.error = { executor: '@nrwl/workspace:run-commands', @@ -203,7 +203,7 @@ describe('Extra Nx Misc Tests', () => { runCLI(`run ${mylib}:error`); fail('Should error if process errors'); } catch (e) { - expect(e.stderr.toString()).toContain( + expect(e.stdout.toString()).toContain( 'Something went wrong in run-commands - Command failed: exit 1' ); } diff --git a/e2e/nx-misc/src/workspace.test.ts b/e2e/nx-misc/src/workspace.test.ts index fb7ec7fed6..e9c7f25f55 100644 --- a/e2e/nx-misc/src/workspace.test.ts +++ b/e2e/nx-misc/src/workspace.test.ts @@ -831,10 +831,10 @@ describe('Workspace Tests', () => { } expect(error).toBeDefined(); - expect(error.stderr.toString()).toContain( + expect(error.stdout.toString()).toContain( `${lib1} is still depended on by the following projects` ); - expect(error.stderr.toString()).toContain(lib2); + expect(error.stdout.toString()).toContain(lib2); /** * Try force removing the project diff --git a/e2e/nx-run/src/affected-graph.test.ts b/e2e/nx-run/src/affected-graph.test.ts index b7141d17ac..b61184d774 100644 --- a/e2e/nx-run/src/affected-graph.test.ts +++ b/e2e/nx-run/src/affected-graph.test.ts @@ -339,44 +339,6 @@ describe('Nx Affected and Graph Tests', () => { }); compareTwoArrays(resWithTarget.projects, [`${myapp}-e2e`, myapp]); - const resWithDeps = JSON.parse( - ( - await runCLIAsync( - `print-affected --files=apps/${myapp}/src/app/app.element.spec.ts --target=build --with-deps`, - { silent: true } - ) - ).stdout - ); - - expect(resWithDeps.tasks[0]).toMatchObject({ - id: `${myapp}:build:production`, - overrides: {}, - target: { - project: myapp, - target: 'build', - }, - command: `${runNx} run ${myapp}:build:production`, - outputs: [`dist/apps/${myapp}`], - }); - - expect(resWithDeps.tasks[2]).toMatchObject({ - id: `${mypublishablelib}:build`, - overrides: {}, - target: { - project: mypublishablelib, - target: 'build', - }, - command: `${runNx} run ${mypublishablelib}:build`, - outputs: [`dist/libs/${mypublishablelib}`], - }); - - compareTwoArrays(resWithDeps.projects, [ - mylib, - mypublishablelib, - myapp, - `${myapp}-e2e`, - ]); - const resWithTargetWithSelect1 = ( await runCLIAsync( `print-affected --files=apps/${myapp}/src/app/app.element.spec.ts --target=test --select=projects`, diff --git a/e2e/nx-run/src/cache.test.ts b/e2e/nx-run/src/cache.test.ts index 54f4886974..97dabf303c 100644 --- a/e2e/nx-run/src/cache.test.ts +++ b/e2e/nx-run/src/cache.test.ts @@ -172,7 +172,10 @@ describe('cache', () => { runCLI(`generate @nrwl/js:lib ${child1}`); runCLI(`generate @nrwl/js:lib ${child2}`); updateJson(`nx.json`, (c) => { - c.namedInputs = { prod: ['!**/*.spec.ts'] }; + c.namedInputs = { + default: ['{projectRoot}/**/*'], + prod: ['!{projectRoot}/**/*.spec.ts'], + }; c.targetDefaults = { test: { inputs: ['default', '^prod'], @@ -187,7 +190,7 @@ describe('cache', () => { }); updateJson(`libs/${child1}/project.json`, (c) => { - c.namedInputs = { prod: ['**/*.ts'] }; + c.namedInputs = { prod: ['{projectRoot}/**/*.ts'] }; return c; }); diff --git a/packages/angular/src/executors/file-server/file-server.impl.ts b/packages/angular/src/executors/file-server/file-server.impl.ts index 43573a45e7..3d183cf6d8 100644 --- a/packages/angular/src/executors/file-server/file-server.impl.ts +++ b/packages/angular/src/executors/file-server/file-server.impl.ts @@ -49,9 +49,6 @@ function getHttpServerArgs(options: Schema) { function getBuildTargetCommand(options: Schema) { const cmd = ['nx', 'run', options.buildTarget]; - if (options.withDeps) { - cmd.push(`--with-deps`); - } if (options.parallel) { cmd.push(`--parallel`); } diff --git a/packages/jest/src/generators/jest-project/lib/update-jestconfig.ts b/packages/jest/src/generators/jest-project/lib/update-jestconfig.ts index 660b244dc6..66f5a0e792 100644 --- a/packages/jest/src/generators/jest-project/lib/update-jestconfig.ts +++ b/packages/jest/src/generators/jest-project/lib/update-jestconfig.ts @@ -19,6 +19,6 @@ export function updateJestConfig(host: Tree, options: JestProjectSchema) { host, findRootJestConfig(host), 'projects', - `/${project.root}` + `/$"14.4.0-beta.5"root}` ); } diff --git a/packages/next/src/utils/buildable-libs.ts b/packages/next/src/utils/buildable-libs.ts index 2d74ebb243..a12ae0723d 100644 --- a/packages/next/src/utils/buildable-libs.ts +++ b/packages/next/src/utils/buildable-libs.ts @@ -24,8 +24,6 @@ export function assertDependentProjectsHaveBeenBuilt( Please build these libraries first: ${missing.map((x) => ` - ${x.node.name}`).join('\n')} - - Try: ${chalk.bold(`nx build ${context.projectName} --with-deps`)} `) ); } diff --git a/packages/nx/presets/core.json b/packages/nx/presets/core.json index 7816bd9f01..5a2a979443 100644 --- a/packages/nx/presets/core.json +++ b/packages/nx/presets/core.json @@ -1,7 +1,4 @@ { - "implicitDependencies": { - "package.json": "*" - }, "tasksRunnerOptions": { "default": { "runner": "nx/tasks-runners/default", diff --git a/packages/nx/src/command-line/affected.ts b/packages/nx/src/command-line/affected.ts index 873631d79d..42eb6d64e0 100644 --- a/packages/nx/src/command-line/affected.ts +++ b/packages/nx/src/command-line/affected.ts @@ -12,7 +12,6 @@ import { } from '../utils/command-line-utils'; import { performance } from 'perf_hooks'; import { createProjectGraphAsync } from '../project-graph/project-graph'; -import { withDeps } from '../project-graph/operators'; import { ProjectGraph, ProjectGraphProjectNode } from '../config/project-graph'; import { projectHasTarget } from '../utils/project-graph-utils'; import { filterAffected } from '../project-graph/affected/affected-project-graph'; @@ -147,12 +146,6 @@ function projectsToRun( nxArgs ) ); - if (!nxArgs.all && nxArgs.withDeps) { - affectedGraph = withDeps( - projectGraph, - Object.values(affectedGraph.nodes) as ProjectGraphProjectNode[] - ); - } if (nxArgs.exclude) { const excludedProjects = new Set(nxArgs.exclude); diff --git a/packages/nx/src/hasher/hasher.spec.ts b/packages/nx/src/hasher/hasher.spec.ts index 398f7259cf..35ada82639 100644 --- a/packages/nx/src/hasher/hasher.spec.ts +++ b/packages/nx/src/hasher/hasher.spec.ts @@ -8,7 +8,6 @@ jest.doMock('../utils/workspace-root', () => { }); jest.mock('fs', () => require('memfs').fs); -require('fs').existsSync = () => true; jest.mock('../utils/typescript'); import { vol } from 'memfs'; @@ -28,22 +27,21 @@ describe('Hasher', () => { }, }, }); - let hashes = { - '/root/yarn.lock': 'yarn.lock.hash', - '/root/nx.json': 'nx.json.hash', - '/root/package-lock.json': 'package-lock.json.hash', - '/root/package.json': 'package.json.hash', - '/root/pnpm-lock.yaml': 'pnpm-lock.yaml.hash', - '/root/tsconfig.base.json': tsConfigBaseJson, - '/root/workspace.json': 'workspace.json.hash', - '/root/global1': 'global1.hash', - '/root/global2': 'global2.hash', - }; + const allWorkspaceFiles = [ + { file: 'yarn.lock', hash: 'yarn.lock.hash' }, + { file: 'nx.json', hash: 'nx.json.hash' }, + { file: 'package-lock.json', hash: 'package-lock.json.hash' }, + { file: 'package.json', hash: 'package.json.hash' }, + { file: 'pnpm-lock.yaml', hash: 'pnpm-lock.yaml.hash' }, + { file: 'tsconfig.base.json', hash: tsConfigBaseJson }, + { file: 'workspace.json', hash: 'workspace.json.hash' }, + { file: 'global1', hash: 'global1.hash' }, + { file: 'global2', hash: 'global2.hash' }, + ]; function createHashing(): any { return { hashArray: (values: string[]) => values.join('|'), - hashFile: (path: string) => hashes[path], }; } @@ -100,6 +98,7 @@ describe('Hasher', () => { dependencies: { parent: [], }, + allWorkspaceFiles, }, {} as any, { @@ -172,6 +171,7 @@ describe('Hasher', () => { dependencies: { parent: [{ source: 'parent', target: 'child', type: 'static' }], }, + allWorkspaceFiles, }, {} as any, {}, @@ -208,8 +208,8 @@ describe('Hasher', () => { }, }, files: [ - { file: '/filea.ts', hash: 'a.hash' }, - { file: '/filea.spec.ts', hash: 'a.spec.hash' }, + { file: 'libs/parent/filea.ts', hash: 'a.hash' }, + { file: 'libs/parent/filea.spec.ts', hash: 'a.spec.hash' }, ], }, }, @@ -223,8 +223,8 @@ describe('Hasher', () => { }, targets: { build: {} }, files: [ - { file: '/fileb.ts', hash: 'b.hash' }, - { file: '/fileb.spec.ts', hash: 'b.spec.hash' }, + { file: 'libs/child/fileb.ts', hash: 'b.hash' }, + { file: 'libs/child/fileb.spec.ts', hash: 'b.spec.hash' }, ], }, }, @@ -232,10 +232,11 @@ describe('Hasher', () => { dependencies: { parent: [{ source: 'parent', target: 'child', type: 'static' }], }, + allWorkspaceFiles, }, { namedInputs: { - prod: ['!**/*.spec.ts'], + prod: ['!{projectRoot}/**/*.spec.ts'], }, } as any, {}, @@ -250,13 +251,13 @@ describe('Hasher', () => { expect(onlySourceNodes(hash.details.nodes)).toEqual({ 'child:$filesets': - '/fileb.ts|/fileb.spec.ts|b.hash|b.spec.hash|{"root":"libs/child","namedInputs":{"prod":["default"]},"targets":{"build":{}}}|{"compilerOptions":{"paths":{"@nrwl/parent":["libs/parent/src/index.ts"],"@nrwl/child":["libs/child/src/index.ts"]}}}', + 'libs/child/fileb.ts|libs/child/fileb.spec.ts|b.hash|b.spec.hash|{"root":"libs/child","namedInputs":{"prod":["default"]},"targets":{"build":{}}}|{"compilerOptions":{"paths":{"@nrwl/parent":["libs/parent/src/index.ts"],"@nrwl/child":["libs/child/src/index.ts"]}}}', 'parent:$filesets': - '/filea.ts|a.hash|{"root":"libs/parent","targets":{"build":{"inputs":["prod","^prod"]}}}|{"compilerOptions":{"paths":{"@nrwl/parent":["libs/parent/src/index.ts"],"@nrwl/child":["libs/child/src/index.ts"]}}}', + 'libs/parent/filea.ts|a.hash|{"root":"libs/parent","targets":{"build":{"inputs":["prod","^prod"]}}}|{"compilerOptions":{"paths":{"@nrwl/parent":["libs/parent/src/index.ts"],"@nrwl/child":["libs/child/src/index.ts"]}}}', }); }); - it('should use targetDefaults from nx.json', async () => { + it('should use targetdefaults from nx.json', async () => { const hasher = new Hasher( { nodes: { @@ -269,8 +270,8 @@ describe('Hasher', () => { build: {}, }, files: [ - { file: '/filea.ts', hash: 'a.hash' }, - { file: '/filea.spec.ts', hash: 'a.spec.hash' }, + { file: 'libs/parent/filea.ts', hash: 'a.hash' }, + { file: 'libs/parent/filea.spec.ts', hash: 'a.spec.hash' }, ], }, }, @@ -281,8 +282,8 @@ describe('Hasher', () => { root: 'libs/child', targets: { build: {} }, files: [ - { file: '/fileb.ts', hash: 'b.hash' }, - { file: '/fileb.spec.ts', hash: 'b.spec.hash' }, + { file: 'libs/child/fileb.ts', hash: 'b.hash' }, + { file: 'libs/child/fileb.spec.ts', hash: 'b.spec.hash' }, ], }, }, @@ -290,10 +291,11 @@ describe('Hasher', () => { dependencies: { parent: [{ source: 'parent', target: 'child', type: 'static' }], }, + allWorkspaceFiles, }, { namedInputs: { - prod: ['!**/*.spec.ts'], + prod: ['!{projectRoot}/**/*.spec.ts'], }, targetDefaults: { build: { @@ -313,9 +315,9 @@ describe('Hasher', () => { expect(onlySourceNodes(hash.details.nodes)).toEqual({ 'child:$filesets': - '/fileb.ts|b.hash|{"root":"libs/child","targets":{"build":{}}}|{"compilerOptions":{"paths":{"@nrwl/parent":["libs/parent/src/index.ts"],"@nrwl/child":["libs/child/src/index.ts"]}}}', + 'libs/child/fileb.ts|b.hash|{"root":"libs/child","targets":{"build":{}}}|{"compilerOptions":{"paths":{"@nrwl/parent":["libs/parent/src/index.ts"],"@nrwl/child":["libs/child/src/index.ts"]}}}', 'parent:$filesets': - '/filea.ts|a.hash|{"root":"libs/parent","targets":{"build":{}}}|{"compilerOptions":{"paths":{"@nrwl/parent":["libs/parent/src/index.ts"],"@nrwl/child":["libs/child/src/index.ts"]}}}', + 'libs/parent/filea.ts|a.hash|{"root":"libs/parent","targets":{"build":{}}}|{"compilerOptions":{"paths":{"@nrwl/parent":["libs/parent/src/index.ts"],"@nrwl/child":["libs/child/src/index.ts"]}}}', }); }); @@ -336,6 +338,7 @@ describe('Hasher', () => { dependencies: { parent: [], }, + allWorkspaceFiles, }, { npmScope: 'nrwl' } as any, { @@ -393,6 +396,7 @@ describe('Hasher', () => { parent: [{ source: 'parent', target: 'child', type: 'static' }], child: [{ source: 'child', target: 'parent', type: 'static' }], }, + allWorkspaceFiles, }, {} as any, {}, @@ -455,6 +459,7 @@ describe('Hasher', () => { dependencies: { parent: [], }, + allWorkspaceFiles, }, {} as any, { @@ -494,16 +499,7 @@ describe('Hasher', () => { }, }, dependencies: {}, - allWorkspaceFiles: [ - { - file: 'global1', - hash: 'hash1', - }, - { - file: 'global2', - hash: 'hash2', - }, - ], + allWorkspaceFiles, }, { implicitDependencies: { @@ -554,6 +550,7 @@ describe('Hasher', () => { { source: 'app', target: 'npm:react', type: DependencyType.static }, ], }, + allWorkspaceFiles, }, {} as any, {}, @@ -599,6 +596,7 @@ describe('Hasher', () => { }, ], }, + allWorkspaceFiles, }, {} as any, {}, diff --git a/packages/nx/src/hasher/hasher.ts b/packages/nx/src/hasher/hasher.ts index eeb0576975..5cc8583afe 100644 --- a/packages/nx/src/hasher/hasher.ts +++ b/packages/nx/src/hasher/hasher.ts @@ -1,6 +1,5 @@ import { exec } from 'child_process'; import * as minimatch from 'minimatch'; -import { existsSync } from 'fs'; import { getRootTsConfigFileName } from '../utils/typescript'; import { defaultHashing, HashingImpl } from './hashing-impl'; import { @@ -14,8 +13,6 @@ import { Task } from '../config/task-graph'; import { readJsonFile } from '../utils/fileutils'; import { InputDefinition } from '../config/workspace-json-project-json'; import { getImportPath } from '../utils/path'; -import { workspaceRoot } from '../utils/workspace-root'; -import { join } from 'path'; type ExpandedSelfInput = | { fileset: string } @@ -183,7 +180,7 @@ export class Hasher { const DEFAULT_INPUTS = [ { projects: 'self', - fileset: 'default', + fileset: '{projectRoot}/**/*', }, { projects: 'dependencies', @@ -283,13 +280,15 @@ class TaskHasher { task: Task, projectNode: ProjectGraphProjectNode ): { depsInputs: { input: string }[]; selfInputs: ExpandedSelfInput[] } { + const namedInputs = { + default: [{ fileset: '{projectRoot}/**/*' }], + ...this.nxJson.namedInputs, + ...projectNode.data.namedInputs, + }; if (task.target.target === '$input') { return { depsInputs: [{ input: task.target.configuration }], - selfInputs: expandNamedInput(task.target.configuration, { - ...this.nxJson.namedInputs, - ...projectNode.data.namedInputs, - }), + selfInputs: expandNamedInput(task.target.configuration, namedInputs), }; } else { const targetData = projectNode.data.targets[task.target.target]; @@ -299,7 +298,7 @@ class TaskHasher { // task from TaskGraph can be added here return splitInputsIntoSelfAndDependencies( targetData.inputs || targetDefaults?.inputs || DEFAULT_INPUTS, - { ...this.nxJson.namedInputs, ...projectNode.data.namedInputs } + namedInputs ); } } @@ -340,25 +339,42 @@ class TaskHasher { .filter((r) => !!r['fileset']) .map((r) => r['fileset']); - const projectFilesets = filesets.filter( - (r) => !r.startsWith('{workspaceRoot}') - ); - - const rootFilesets = filesets.filter((r) => - r.startsWith('{workspaceRoot}/') - ); + const projectFilesets = []; + const workspaceFilesets = []; + let invalidFileset = null; + for (let f of filesets) { + if (f.startsWith('{projectRoot}/') || f.startsWith('!{projectRoot}/')) { + projectFilesets.push(f); + } else if ( + f.startsWith('{workspaceRoot}/') || + f.startsWith('!{workspaceRoot}/') + ) { + workspaceFilesets.push(f); + } else { + invalidFileset = f; + } + } + if (invalidFileset) { + throw new Error( + [ + `"${invalidFileset}" is an invalid fileset.`, + 'All filesets have to start with either {workspaceRoot} or {projectRoot}.', + 'For instance: "!{projectRoot}/**/*.spec.ts" or "{workspaceRoot}/package.json".', + `If "${invalidFileset}" is a named input, make sure it is defined in, for instance, nx.json.`, + ].join('\n') + ); + } + const notFilesets = inputs.filter((r) => !r['fileset']); return Promise.all([ this.hashTaskFileset(task, projectFilesets), ...[ - ...rootFilesets, + ...workspaceFilesets, ...this.legacyFilesetInputs.map((r) => r.fileset), ].map((fileset) => this.hashRootFileset(fileset)), - ...[...inputs, ...this.legacyRuntimeInputs] - .filter((r) => !r['fileset']) - .map((r) => - r['runtime'] ? this.hashRuntime(r['runtime']) : this.hashEnv(r['env']) - ), + ...[...notFilesets, ...this.legacyRuntimeInputs].map((r) => + r['runtime'] ? this.hashRuntime(r['runtime']) : this.hashEnv(r['env']) + ), ]); } @@ -372,13 +388,14 @@ class TaskHasher { this.projectGraph.allWorkspaceFiles .filter((f) => minimatch(f.file, withoutWorkspaceRoot)) .forEach((f) => { - parts.push(this.hashing.hashFile(join(workspaceRoot, f.file))); + parts.push(f.hash); }); } else { - if (existsSync(join(workspaceRoot, withoutWorkspaceRoot))) { - parts.push( - this.hashing.hashFile(join(workspaceRoot, withoutWorkspaceRoot)) - ); + const matchingFile = this.projectGraph.allWorkspaceFiles.find( + (t) => t.file === withoutWorkspaceRoot + ); + if (matchingFile) { + parts.push(matchingFile.hash); } } const value = this.hashing.hashArray(parts); @@ -399,7 +416,14 @@ class TaskHasher { if (!this.filesetHashes[mapKey]) { this.filesetHashes[mapKey] = new Promise(async (res) => { const p = this.projectGraph.nodes[task.target.project]; - const filteredFiles = this.filterFiles(p.data.files, filesetPatterns); + const filesetWithExpandedProjectRoot = filesetPatterns.map((f) => + f.replace('{projectRoot}', p.data.root) + ); + const filteredFiles = this.filterFiles( + p.data.root, + p.data.files, + filesetWithExpandedProjectRoot + ); const fileNames = filteredFiles.map((f) => f.file); const values = filteredFiles.map((f) => f.hash); @@ -450,9 +474,12 @@ class TaskHasher { }; } - private filterFiles(files: FileData[], patterns: string[]) { - patterns = patterns.filter((p) => p !== 'default'); - if (patterns.length === 0) return files; + private filterFiles( + projectRoot: string, + files: FileData[], + patterns: string[] + ) { + if (patterns.indexOf(`${projectRoot}/**/*`) > -1) return files; return files.filter( (f) => !!patterns.find((pattern) => minimatch(f.file, pattern)) ); @@ -545,7 +572,6 @@ export function expandNamedInput( input: string, namedInputs: { [inputName: string]: (InputDefinition | string)[] } ): ExpandedSelfInput[] { - if (input === 'default') return [{ fileset: 'default' }]; namedInputs ||= {}; if (!namedInputs[input]) throw new Error(`Input '${input}' is not defined`); return expandSelfInputs(namedInputs[input], namedInputs); diff --git a/packages/nx/src/project-graph/affected/locators/workspace-projects.spec.ts b/packages/nx/src/project-graph/affected/locators/workspace-projects.spec.ts index d8b09f5d7d..34dd7cf945 100644 --- a/packages/nx/src/project-graph/affected/locators/workspace-projects.spec.ts +++ b/packages/nx/src/project-graph/affected/locators/workspace-projects.spec.ts @@ -7,6 +7,7 @@ import { WholeFileChange } from '../../file-utils'; import { getTouchedProjects, getImplicitlyTouchedProjects, + extractGlobalFilesFromInputs, } from './workspace-projects'; function getFileChanges(files: string[]) { @@ -147,6 +148,56 @@ describe('getImplicitlyTouchedProjects', () => { }); }); +describe('extractGlobalFilesFromInputs', () => { + it('should return list of global files from nx.json', () => { + const globalFiles = extractGlobalFilesFromInputs( + { + namedInputs: { + one: [ + '{workspaceRoot}/global1.txt', + { fileset: '{workspaceRoot}/global2.txt' }, + '{projectRoot}/local.txt', + ], + }, + targetDefaults: { + build: { + inputs: ['{workspaceRoot}/global3.txt'], + }, + }, + }, + {} + ); + expect(globalFiles).toEqual(['global1.txt', 'global2.txt', 'global3.txt']); + }); + + it('should return list of global files from project configuration', () => { + const globalFiles = extractGlobalFilesFromInputs( + {}, + { + one: { + name: 'one', + type: 'lib', + data: { + namedInputs: { + one: [ + '{workspaceRoot}/global1.txt', + { fileset: '{workspaceRoot}/global2.txt' }, + '{projectRoot}/local.txt', + ], + }, + targets: { + build: { + inputs: ['{workspaceRoot}/global3.txt'], + }, + }, + }, + }, + } + ); + expect(globalFiles).toEqual(['global1.txt', 'global2.txt', 'global3.txt']); + }); +}); + function buildProjectGraphNodes( projects: Record ): ProjectGraph['nodes'] { diff --git a/packages/nx/src/project-graph/affected/locators/workspace-projects.ts b/packages/nx/src/project-graph/affected/locators/workspace-projects.ts index 48b51ae040..6ce046c773 100644 --- a/packages/nx/src/project-graph/affected/locators/workspace-projects.ts +++ b/packages/nx/src/project-graph/affected/locators/workspace-projects.ts @@ -1,5 +1,7 @@ import * as minimatch from 'minimatch'; import { TouchedProjectLocator } from '../affected-project-graph-models'; +import { NxJsonConfiguration } from '../../../config/nx-json'; +import { ProjectGraphProjectNode } from '../../../config/project-graph'; export const getTouchedProjects: TouchedProjectLocator = ( touchedFiles, @@ -26,18 +28,22 @@ export const getTouchedProjects: TouchedProjectLocator = ( export const getImplicitlyTouchedProjects: TouchedProjectLocator = ( fileChanges, - workspaceJson, + projectGraphNodes, nxJson ): string[] => { - if (!nxJson.implicitDependencies) { - return []; - } + const implicits = { ...nxJson.implicitDependencies }; + const globalFiles = [ + ...extractGlobalFilesFromInputs(nxJson, projectGraphNodes), + 'nx.json', + 'package.json', + ]; + globalFiles.forEach((file) => { + implicits[file] = '*' as any; + }); const touched = new Set(); - for (const [pattern, projects] of Object.entries( - nxJson.implicitDependencies - )) { + for (const [pattern, projects] of Object.entries(implicits)) { const implicitDependencyWasChanged = fileChanges.some((f) => minimatch(f.file, pattern) ); @@ -53,3 +59,49 @@ export const getImplicitlyTouchedProjects: TouchedProjectLocator = ( return Array.from(touched); }; + +export function extractGlobalFilesFromInputs( + nxJson: NxJsonConfiguration, + projectGraphNodes: Record +) { + const globalFiles = []; + globalFiles.push(...extractGlobalFilesFromNamedInputs(nxJson.namedInputs)); + globalFiles.push(...extractGlobalFilesFromTargets(nxJson.targetDefaults)); + Object.values(projectGraphNodes || {}).forEach((node) => { + globalFiles.push( + ...extractGlobalFilesFromNamedInputs(node.data.namedInputs) + ); + globalFiles.push(...extractGlobalFilesFromTargets(node.data.targets)); + }); + return globalFiles; +} + +function extractGlobalFilesFromNamedInputs(namedInputs: any) { + const globalFiles = []; + for (const inputs of Object.values(namedInputs || {})) { + globalFiles.push(...extractGlobalFiles(inputs)); + } + return globalFiles; +} + +function extractGlobalFilesFromTargets(targets: any) { + const globalFiles = []; + for (const target of Object.values(targets || {})) { + if ((target as any).inputs) { + globalFiles.push(...extractGlobalFiles((target as any).inputs)); + } + } + return globalFiles; +} + +function extractGlobalFiles(inputs: any) { + const globalFiles = []; + for (const input of inputs) { + if (typeof input === 'string' && input.startsWith('{workspaceRoot}/')) { + globalFiles.push(input.substring('{workspaceRoot}/'.length)); + } else if (input.fileset && input.fileset.startsWith('{workspaceRoot}/')) { + globalFiles.push(input.fileset.substring('{workspaceRoot}/'.length)); + } + } + return globalFiles; +} diff --git a/packages/nx/src/project-graph/operators.spec.ts b/packages/nx/src/project-graph/operators.spec.ts index fc3b371b00..8b9de1d417 100644 --- a/packages/nx/src/project-graph/operators.spec.ts +++ b/packages/nx/src/project-graph/operators.spec.ts @@ -1,4 +1,4 @@ -import { reverse, withDeps, filterNodes } from './operators'; +import { reverse, filterNodes } from './operators'; import { DependencyType, ProjectGraph, @@ -163,144 +163,6 @@ describe('reverse', () => { }); }); -describe('withDeps', () => { - it('should return a new graph with all dependencies included from original', () => { - const affectedNodes: ProjectGraphProjectNode[] = [ - { name: 'app1-e2e', type: 'app', data: null }, - { name: 'app1', type: 'app', data: null }, - { name: 'lib1', type: 'lib', data: null }, - ]; - - const result = withDeps(graph, affectedNodes); - expect(result).toEqual({ - nodes: { - lib3: { - name: 'lib3', - type: 'lib', - data: null, - }, - lib2: { - name: 'lib2', - type: 'lib', - data: null, - }, - lib1: { - name: 'lib1', - type: 'lib', - data: null, - }, - app1: { - name: 'app1', - type: 'app', - data: null, - }, - 'app1-e2e': { - name: 'app1-e2e', - type: 'app', - data: null, - }, - }, - dependencies: { - lib2: [ - { - type: 'static', - source: 'lib2', - target: 'lib3', - }, - ], - lib1: [ - { - type: 'static', - source: 'lib1', - target: 'lib2', - }, - { - type: 'static', - source: 'lib1', - target: 'lib3', - }, - ], - app1: [ - { - type: 'static', - source: 'app1', - target: 'lib1', - }, - ], - 'app1-e2e': [ - { - type: 'implicit', - source: 'app1-e2e', - target: 'app1', - }, - ], - lib3: [], - }, - }); - }); - - it('should handle circular deps', () => { - const graph: ProjectGraph = { - nodes: { - lib1: { name: 'lib1', type: 'lib', data: null }, - lib2: { name: 'lib2', type: 'lib', data: null }, - }, - dependencies: { - lib1: [ - { - type: DependencyType.static, - source: 'lib1', - target: 'lib2', - }, - ], - lib2: [ - { - type: DependencyType.static, - source: 'lib2', - target: 'lib1', - }, - ], - }, - }; - - const affectedNodes: ProjectGraphProjectNode[] = [ - { name: 'lib1', type: 'lib', data: null }, - ]; - - const result = withDeps(graph, affectedNodes); - expect(result).toEqual({ - nodes: { - lib1: { - name: 'lib1', - type: 'lib', - data: null, - }, - lib2: { - name: 'lib2', - type: 'lib', - data: null, - }, - }, - dependencies: { - lib2: [ - { - type: 'static', - source: 'lib2', - target: 'lib1', - }, - ], - lib1: [ - { - type: 'static', - source: 'lib1', - target: 'lib2', - }, - ], - }, - }); - }); -}); - describe('filterNodes', () => { it('filters out nodes based on predicate', () => { const result = filterNodes((n) => n.type === 'app')(graph); diff --git a/packages/nx/src/tasks-runner/create-task-graph.ts b/packages/nx/src/tasks-runner/create-task-graph.ts index 2926eeccb0..16805e893a 100644 --- a/packages/nx/src/tasks-runner/create-task-graph.ts +++ b/packages/nx/src/tasks-runner/create-task-graph.ts @@ -219,7 +219,12 @@ function interpolateOverrides( Object.entries(interpolatedArgs).forEach(([name, value]) => { interpolatedArgs[name] = typeof value === 'string' - ? interpolate(value, { project: { ...project, name: projectName } }) + ? interpolate(value, { + workspaceRoot: '', + projectRoot: project.root, + projectName: project.name, + project: { ...project, name: projectName }, // this is legacy + }) : value; }); return interpolatedArgs; diff --git a/packages/nx/src/tasks-runner/default-tasks-runner.ts b/packages/nx/src/tasks-runner/default-tasks-runner.ts index c98e1ee6aa..5e370eaf9b 100644 --- a/packages/nx/src/tasks-runner/default-tasks-runner.ts +++ b/packages/nx/src/tasks-runner/default-tasks-runner.ts @@ -55,10 +55,6 @@ export const defaultTasksRunner: TasksRunner< options.lifeCycle.startCommand(); try { return await runAllTasks(tasks, options, context); - } catch (e) { - console.error('Unexpected error:'); - console.error(e); - process.exit(1); } finally { options.lifeCycle.endCommand(); } diff --git a/packages/nx/src/tasks-runner/run-command.ts b/packages/nx/src/tasks-runner/run-command.ts index ca8a0b41d6..5c74b48820 100644 --- a/packages/nx/src/tasks-runner/run-command.ts +++ b/packages/nx/src/tasks-runner/run-command.ts @@ -22,7 +22,8 @@ import { import { Task } from '../config/task-graph'; import { createTaskGraph } from './create-task-graph'; import { findCycle, makeAcyclic } from './task-graph-utils'; -import { TargetDependencyConfig } from 'nx/src/config/workspace-json-project-json'; +import { TargetDependencyConfig } from '../config/workspace-json-project-json'; +import { handleErrors } from '../utils/params'; async function getTerminalOutputLifeCycle( initiatingProject: string, @@ -90,81 +91,80 @@ export async function runCommand( initiatingProject: string | null, extraTargetDependencies: Record ) { - const { tasksRunner, runnerOptions } = getRunner(nxArgs, nxJson); + const status = await handleErrors(overrides['verbose'] === true, async () => { + const { tasksRunner, runnerOptions } = getRunner(nxArgs, nxJson); - const defaultDependencyConfigs = mergeTargetDependencies( - nxJson.targetDefaults, - extraTargetDependencies - ); - const projectNames = projectsToRun.map((t) => t.name); - const taskGraph = createTaskGraph( - projectGraph, - defaultDependencyConfigs, - projectNames, - [nxArgs.target], - nxArgs.configuration, - overrides - ); - - const cycle = findCycle(taskGraph); - if (cycle) { - if (nxArgs.nxIgnoreCycles) { - output.warn({ - title: `The task graph has a circular dependency`, - bodyLines: [`${cycle.join(' --> ')}`], - }); - makeAcyclic(taskGraph); - } else { - output.error({ - title: `Could not execute command because the task graph has a circular dependency`, - bodyLines: [`${cycle.join(' --> ')}`], - }); - process.exit(1); - } - } - - const tasks = Object.values(taskGraph.tasks); - if (nxArgs.outputStyle == 'stream') { - process.env.NX_STREAM_OUTPUT = 'true'; - process.env.NX_PREFIX_OUTPUT = 'true'; - } - if (nxArgs.outputStyle == 'stream-without-prefixes') { - process.env.NX_STREAM_OUTPUT = 'true'; - } - const { lifeCycle, renderIsDone } = await getTerminalOutputLifeCycle( - initiatingProject, - projectNames, - tasks, - nxArgs, - overrides, - runnerOptions - ); - const lifeCycles = [lifeCycle] as LifeCycle[]; - - if (process.env.NX_PERF_LOGGING) { - lifeCycles.push(new TaskTimingsLifeCycle()); - } - - if (process.env.NX_PROFILE) { - lifeCycles.push(new TaskProfilingLifeCycle(process.env.NX_PROFILE)); - } - - const promiseOrObservable = tasksRunner( - tasks, - { ...runnerOptions, lifeCycle: new CompositeLifeCycle(lifeCycles) }, - { - initiatingProject: - nxArgs.outputStyle === 'compact' ? null : initiatingProject, - target: nxArgs.target, + const defaultDependencyConfigs = mergeTargetDependencies( + nxJson.targetDefaults, + extraTargetDependencies + ); + const projectNames = projectsToRun.map((t) => t.name); + const taskGraph = createTaskGraph( projectGraph, - nxJson, - nxArgs, - taskGraph, - } - ); + defaultDependencyConfigs, + projectNames, + [nxArgs.target], + nxArgs.configuration, + overrides + ); - let anyFailures; - try { + const cycle = findCycle(taskGraph); + if (cycle) { + if (nxArgs.nxIgnoreCycles) { + output.warn({ + title: `The task graph has a circular dependency`, + bodyLines: [`${cycle.join(' --> ')}`], + }); + makeAcyclic(taskGraph); + } else { + output.error({ + title: `Could not execute command because the task graph has a circular dependency`, + bodyLines: [`${cycle.join(' --> ')}`], + }); + process.exit(1); + } + } + + const tasks = Object.values(taskGraph.tasks); + if (nxArgs.outputStyle == 'stream') { + process.env.NX_STREAM_OUTPUT = 'true'; + process.env.NX_PREFIX_OUTPUT = 'true'; + } + if (nxArgs.outputStyle == 'stream-without-prefixes') { + process.env.NX_STREAM_OUTPUT = 'true'; + } + const { lifeCycle, renderIsDone } = await getTerminalOutputLifeCycle( + initiatingProject, + projectNames, + tasks, + nxArgs, + overrides, + runnerOptions + ); + const lifeCycles = [lifeCycle] as LifeCycle[]; + + if (process.env.NX_PERF_LOGGING) { + lifeCycles.push(new TaskTimingsLifeCycle()); + } + + if (process.env.NX_PROFILE) { + lifeCycles.push(new TaskProfilingLifeCycle(process.env.NX_PROFILE)); + } + + const promiseOrObservable = tasksRunner( + tasks, + { ...runnerOptions, lifeCycle: new CompositeLifeCycle(lifeCycles) }, + { + initiatingProject: + nxArgs.outputStyle === 'compact' ? null : initiatingProject, + target: nxArgs.target, + projectGraph, + nxJson, + nxArgs, + taskGraph, + } + ); + let anyFailures; if ((promiseOrObservable as any).subscribe) { anyFailures = await anyFailuresInObservable(promiseOrObservable); } else { @@ -172,18 +172,11 @@ export async function runCommand( anyFailures = await anyFailuresInPromise(promiseOrObservable as any); } await renderIsDone; - } catch (e) { - output.error({ - title: 'Unhandled error in task executor', - }); - console.error(e); - process.exit(1); - } - + return anyFailures ? 1 : 0; + }); // fix for https://github.com/nrwl/nx/issues/1666 if (process.stdin['unref']) (process.stdin as any).unref(); - - process.exit(anyFailures ? 1 : 0); + process.exit(status); } function mergeTargetDependencies( @@ -261,10 +254,7 @@ export function getRunner( let runner = nxArgs.runner; runner = runner || 'default'; if (!nxJson.tasksRunnerOptions) { - output.error({ - title: `Could not find any runner configurations in nx.json`, - }); - process.exit(1); + throw new Error(`Could not find any runner configurations in nx.json`); } if (nxJson.tasksRunnerOptions[runner]) { let modulePath: string = nxJson.tasksRunnerOptions[runner].runner; @@ -292,9 +282,6 @@ export function getRunner( }, }; } else { - output.error({ - title: `Could not find runner configuration for ${runner}`, - }); - process.exit(1); + throw new Error(`Could not find runner configuration for ${runner}`); } } diff --git a/packages/nx/src/tasks-runner/utils.spec.ts b/packages/nx/src/tasks-runner/utils.spec.ts index 7cfe13d9fe..20110cc083 100644 --- a/packages/nx/src/tasks-runner/utils.spec.ts +++ b/packages/nx/src/tasks-runner/utils.spec.ts @@ -68,7 +68,7 @@ describe('utils', () => { getOutputsForTargetAndConfiguration( task, getNode({ - outputs: ['{project.root}/sub', 'two'], + outputs: ['{projectRoot}/sub', 'two'], options: { myVar: 'one', }, diff --git a/packages/nx/src/tasks-runner/utils.ts b/packages/nx/src/tasks-runner/utils.ts index bba8e27d91..82c9539ca7 100644 --- a/packages/nx/src/tasks-runner/utils.ts +++ b/packages/nx/src/tasks-runner/utils.ts @@ -97,8 +97,11 @@ export function getOutputsForTargetAndConfiguration( return targets.outputs .map((output: string) => { const interpolated = interpolate(output, { + workspaceRoot: '', // this is to make sure interpolation works + projectRoot: node.data.root, + projectName: node.data.name, + project: { ...node.data, name: node.data.name }, // this is legacy options, - project: { ...node.data, name: node.name }, }); return isRelativePath(interpolated) ? joinPathFragments(node.data.root, interpolated) diff --git a/packages/nx/src/utils/command-line-utils.ts b/packages/nx/src/utils/command-line-utils.ts index c72fc0c089..ae7900006d 100644 --- a/packages/nx/src/utils/command-line-utils.ts +++ b/packages/nx/src/utils/command-line-utils.ts @@ -76,7 +76,6 @@ const runOne: string[] = [ 'exclude', 'onlyFailed', 'help', - 'withDeps', 'skipNxCache', 'scan', 'outputStyle', @@ -120,7 +119,6 @@ export interface NxArgs { help?: boolean; version?: boolean; plain?: boolean; - withDeps?: boolean; projects?: string[]; select?: string; skipNxCache?: boolean; diff --git a/packages/nx/src/utils/params.ts b/packages/nx/src/utils/params.ts index 09d1349b4b..ca393ed0b0 100644 --- a/packages/nx/src/utils/params.ts +++ b/packages/nx/src/utils/params.ts @@ -4,7 +4,7 @@ import { TargetConfiguration, ProjectsConfigurations, } from '../config/workspace-json-project-json'; -import { execSync } from 'child_process'; +import { output } from './output'; type PropertyDescription = { type?: string | string[]; @@ -77,16 +77,21 @@ export async function handleErrors(isVerbose: boolean, fn: Function) { return await fn(); } catch (err) { err ??= new Error('Unknown error caught'); - if (err.constructor.name === 'UnsuccessfulWorkflowExecution') { logger.error('The generator workflow failed. See above.'); - } else if (err.message) { - logger.error(err.message); } else { - logger.error(err); - } - if (isVerbose && err.stack) { - logger.info(err.stack); + const lines = (err.message ? err.message : err.toString()).split('\n'); + const bodyLines = lines.slice(1); + if (err.stack && !isVerbose) { + bodyLines.push('Pass --verbose to see the stacktrace.'); + } + output.error({ + title: lines[0], + bodyLines, + }); + if (err.stack && isVerbose) { + logger.info(err.stack); + } } return 1; } diff --git a/packages/web/src/executors/file-server/file-server.impl.ts b/packages/web/src/executors/file-server/file-server.impl.ts index 78b09d100c..72a4e42d38 100644 --- a/packages/web/src/executors/file-server/file-server.impl.ts +++ b/packages/web/src/executors/file-server/file-server.impl.ts @@ -47,9 +47,6 @@ function getHttpServerArgs(options: Schema) { function getBuildTargetCommand(options: Schema) { const cmd = ['nx', 'run', options.buildTarget]; - if (options.withDeps) { - cmd.push(`--with-deps`); - } if (options.parallel) { cmd.push(`--parallel`); } diff --git a/packages/workspace/src/utilities/buildable-libs-utils.ts b/packages/workspace/src/utilities/buildable-libs-utils.ts index 85b669a92e..9b62221341 100644 --- a/packages/workspace/src/utilities/buildable-libs-utils.ts +++ b/packages/workspace/src/utilities/buildable-libs-utils.ts @@ -212,20 +212,13 @@ export function checkDependentProjectsHaveBeenBuilt( targetName, projectDependencies ); - if (missing.length === projectDependencies.length && missing.length > 0) { + if (missing.length > 0) { console.error(stripIndents` It looks like all of ${projectName}'s dependencies have not been built yet: ${missing.map((x) => ` - ${x.node.name}`).join('\n')} - You might be missing a "targetDefaults" configuration in your root nx.json (https://nx.dev/configuration/packagejson#target-defaults), - or "dependsOn" configured in ${projectName}'s angular.json/workspace.json record or project.json (https://nx.dev/configuration/packagejson#dependson) - `); - } else if (missing.length > 0) { - console.error(stripIndents` - Some of the project ${projectName}'s dependencies have not been built yet. Please build these libraries before: - ${missing.map((x) => ` - ${x.node.name}`).join('\n')} - - Try: nx run ${projectName}:${targetName} --with-deps + You might be missing a "targetDefaults" configuration in your root nx.json (https://nx.dev/configuration/projectjson#target-defaults), + or "dependsOn" configured in ${projectName}'s project.json (https://nx.dev/configuration/projectjson#dependson) `); return false; } else {