feat(core): finalize the input api

This commit is contained in:
Victor Savkin 2022-06-28 13:33:27 -04:00
parent dc7301634a
commit c52a8c2e9b
25 changed files with 320 additions and 446 deletions

View File

@ -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

View File

@ -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)

View File

@ -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'
);
}

View File

@ -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

View File

@ -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`,

View File

@ -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;
});

View File

@ -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`);
}

View File

@ -19,6 +19,6 @@ export function updateJestConfig(host: Tree, options: JestProjectSchema) {
host,
findRootJestConfig(host),
'projects',
`<rootDir>/${project.root}`
`<rootDir>/$"14.4.0-beta.5"root}`
);
}

View File

@ -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`)}
`)
);
}

View File

@ -1,7 +1,4 @@
{
"implicitDependencies": {
"package.json": "*"
},
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",

View File

@ -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);

View File

@ -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,
{},

View File

@ -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<any>
): { 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);

View File

@ -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<string, ProjectConfiguration>
): ProjectGraph['nodes'] {

View File

@ -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<string>();
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<string, ProjectGraphProjectNode>
) {
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;
}

View File

@ -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);

View File

@ -219,7 +219,12 @@ function interpolateOverrides<T = any>(
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;

View File

@ -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();
}

View File

@ -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<string, (TargetDependencyConfig | string)[]>
) {
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}`);
}
}

View File

@ -68,7 +68,7 @@ describe('utils', () => {
getOutputsForTargetAndConfiguration(
task,
getNode({
outputs: ['{project.root}/sub', 'two'],
outputs: ['{projectRoot}/sub', 'two'],
options: {
myVar: 'one',
},

View File

@ -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)

View File

@ -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;

View File

@ -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;
}

View File

@ -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`);
}

View File

@ -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 {