feat(core): finalize the input api
This commit is contained in:
parent
dc7301634a
commit
c52a8c2e9b
@ -204,9 +204,7 @@ jobs:
|
|||||||
mainmacos:
|
mainmacos:
|
||||||
executor: macos
|
executor: macos
|
||||||
environment:
|
environment:
|
||||||
NX_CLOUD_DISTRIBUTED_EXECUTION: 'true'
|
|
||||||
NX_E2E_CI_CACHE_KEY: e2e-circleci-macos
|
NX_E2E_CI_CACHE_KEY: e2e-circleci-macos
|
||||||
NX_CLOUD_DISTRIBUTED_EXECUTION_AGENT_COUNT: 2
|
|
||||||
steps:
|
steps:
|
||||||
- run:
|
- run:
|
||||||
name: Set dynamic nx run variable
|
name: Set dynamic nx run variable
|
||||||
@ -249,14 +247,6 @@ workflows:
|
|||||||
name: 'agent7'
|
name: 'agent7'
|
||||||
- agent:
|
- agent:
|
||||||
name: 'agent8'
|
name: 'agent8'
|
||||||
- agent:
|
|
||||||
name: 'agent9'
|
|
||||||
pm: 'npm'
|
|
||||||
os: 'macos'
|
|
||||||
- agent:
|
|
||||||
name: 'agent10'
|
|
||||||
pm: 'npm'
|
|
||||||
os: 'macos'
|
|
||||||
- main-linux
|
- main-linux
|
||||||
- mainmacos:
|
- mainmacos:
|
||||||
name: main-macos-e2e
|
name: main-macos-e2e
|
||||||
|
|||||||
@ -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.
|
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
|
## What's Next
|
||||||
|
|
||||||
- Continue to [Step 7: Test affected projects](/node-tutorial/07-test-affected-projects)
|
- Continue to [Step 7: Test affected projects](/node-tutorial/07-test-affected-projects)
|
||||||
|
|||||||
@ -188,7 +188,7 @@ describe('Extra Nx Misc Tests', () => {
|
|||||||
expect(resultArgs).toContain('camel: d');
|
expect(resultArgs).toContain('camel: d');
|
||||||
}, 120000);
|
}, 120000);
|
||||||
|
|
||||||
it('should fail when a process exits non-zero', () => {
|
it('ttt should fail when a process exits non-zero', () => {
|
||||||
updateProjectConfig(mylib, (config) => {
|
updateProjectConfig(mylib, (config) => {
|
||||||
config.targets.error = {
|
config.targets.error = {
|
||||||
executor: '@nrwl/workspace:run-commands',
|
executor: '@nrwl/workspace:run-commands',
|
||||||
@ -203,7 +203,7 @@ describe('Extra Nx Misc Tests', () => {
|
|||||||
runCLI(`run ${mylib}:error`);
|
runCLI(`run ${mylib}:error`);
|
||||||
fail('Should error if process errors');
|
fail('Should error if process errors');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
expect(e.stderr.toString()).toContain(
|
expect(e.stdout.toString()).toContain(
|
||||||
'Something went wrong in run-commands - Command failed: exit 1'
|
'Something went wrong in run-commands - Command failed: exit 1'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -831,10 +831,10 @@ describe('Workspace Tests', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expect(error).toBeDefined();
|
expect(error).toBeDefined();
|
||||||
expect(error.stderr.toString()).toContain(
|
expect(error.stdout.toString()).toContain(
|
||||||
`${lib1} is still depended on by the following projects`
|
`${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
|
* Try force removing the project
|
||||||
|
|||||||
@ -339,44 +339,6 @@ describe('Nx Affected and Graph Tests', () => {
|
|||||||
});
|
});
|
||||||
compareTwoArrays(resWithTarget.projects, [`${myapp}-e2e`, myapp]);
|
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 = (
|
const resWithTargetWithSelect1 = (
|
||||||
await runCLIAsync(
|
await runCLIAsync(
|
||||||
`print-affected --files=apps/${myapp}/src/app/app.element.spec.ts --target=test --select=projects`,
|
`print-affected --files=apps/${myapp}/src/app/app.element.spec.ts --target=test --select=projects`,
|
||||||
|
|||||||
@ -172,7 +172,10 @@ describe('cache', () => {
|
|||||||
runCLI(`generate @nrwl/js:lib ${child1}`);
|
runCLI(`generate @nrwl/js:lib ${child1}`);
|
||||||
runCLI(`generate @nrwl/js:lib ${child2}`);
|
runCLI(`generate @nrwl/js:lib ${child2}`);
|
||||||
updateJson(`nx.json`, (c) => {
|
updateJson(`nx.json`, (c) => {
|
||||||
c.namedInputs = { prod: ['!**/*.spec.ts'] };
|
c.namedInputs = {
|
||||||
|
default: ['{projectRoot}/**/*'],
|
||||||
|
prod: ['!{projectRoot}/**/*.spec.ts'],
|
||||||
|
};
|
||||||
c.targetDefaults = {
|
c.targetDefaults = {
|
||||||
test: {
|
test: {
|
||||||
inputs: ['default', '^prod'],
|
inputs: ['default', '^prod'],
|
||||||
@ -187,7 +190,7 @@ describe('cache', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
updateJson(`libs/${child1}/project.json`, (c) => {
|
updateJson(`libs/${child1}/project.json`, (c) => {
|
||||||
c.namedInputs = { prod: ['**/*.ts'] };
|
c.namedInputs = { prod: ['{projectRoot}/**/*.ts'] };
|
||||||
return c;
|
return c;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -49,9 +49,6 @@ function getHttpServerArgs(options: Schema) {
|
|||||||
|
|
||||||
function getBuildTargetCommand(options: Schema) {
|
function getBuildTargetCommand(options: Schema) {
|
||||||
const cmd = ['nx', 'run', options.buildTarget];
|
const cmd = ['nx', 'run', options.buildTarget];
|
||||||
if (options.withDeps) {
|
|
||||||
cmd.push(`--with-deps`);
|
|
||||||
}
|
|
||||||
if (options.parallel) {
|
if (options.parallel) {
|
||||||
cmd.push(`--parallel`);
|
cmd.push(`--parallel`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,6 @@ export function updateJestConfig(host: Tree, options: JestProjectSchema) {
|
|||||||
host,
|
host,
|
||||||
findRootJestConfig(host),
|
findRootJestConfig(host),
|
||||||
'projects',
|
'projects',
|
||||||
`<rootDir>/${project.root}`
|
`<rootDir>/$"14.4.0-beta.5"root}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,8 +24,6 @@ export function assertDependentProjectsHaveBeenBuilt(
|
|||||||
|
|
||||||
Please build these libraries first:
|
Please build these libraries first:
|
||||||
${missing.map((x) => ` - ${x.node.name}`).join('\n')}
|
${missing.map((x) => ` - ${x.node.name}`).join('\n')}
|
||||||
|
|
||||||
Try: ${chalk.bold(`nx build ${context.projectName} --with-deps`)}
|
|
||||||
`)
|
`)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,4 @@
|
|||||||
{
|
{
|
||||||
"implicitDependencies": {
|
|
||||||
"package.json": "*"
|
|
||||||
},
|
|
||||||
"tasksRunnerOptions": {
|
"tasksRunnerOptions": {
|
||||||
"default": {
|
"default": {
|
||||||
"runner": "nx/tasks-runners/default",
|
"runner": "nx/tasks-runners/default",
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import {
|
|||||||
} from '../utils/command-line-utils';
|
} from '../utils/command-line-utils';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
import { createProjectGraphAsync } from '../project-graph/project-graph';
|
import { createProjectGraphAsync } from '../project-graph/project-graph';
|
||||||
import { withDeps } from '../project-graph/operators';
|
|
||||||
import { ProjectGraph, ProjectGraphProjectNode } from '../config/project-graph';
|
import { ProjectGraph, ProjectGraphProjectNode } from '../config/project-graph';
|
||||||
import { projectHasTarget } from '../utils/project-graph-utils';
|
import { projectHasTarget } from '../utils/project-graph-utils';
|
||||||
import { filterAffected } from '../project-graph/affected/affected-project-graph';
|
import { filterAffected } from '../project-graph/affected/affected-project-graph';
|
||||||
@ -147,12 +146,6 @@ function projectsToRun(
|
|||||||
nxArgs
|
nxArgs
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if (!nxArgs.all && nxArgs.withDeps) {
|
|
||||||
affectedGraph = withDeps(
|
|
||||||
projectGraph,
|
|
||||||
Object.values(affectedGraph.nodes) as ProjectGraphProjectNode[]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nxArgs.exclude) {
|
if (nxArgs.exclude) {
|
||||||
const excludedProjects = new Set(nxArgs.exclude);
|
const excludedProjects = new Set(nxArgs.exclude);
|
||||||
|
|||||||
@ -8,7 +8,6 @@ jest.doMock('../utils/workspace-root', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
jest.mock('fs', () => require('memfs').fs);
|
jest.mock('fs', () => require('memfs').fs);
|
||||||
require('fs').existsSync = () => true;
|
|
||||||
jest.mock('../utils/typescript');
|
jest.mock('../utils/typescript');
|
||||||
|
|
||||||
import { vol } from 'memfs';
|
import { vol } from 'memfs';
|
||||||
@ -28,22 +27,21 @@ describe('Hasher', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let hashes = {
|
const allWorkspaceFiles = [
|
||||||
'/root/yarn.lock': 'yarn.lock.hash',
|
{ file: 'yarn.lock', hash: 'yarn.lock.hash' },
|
||||||
'/root/nx.json': 'nx.json.hash',
|
{ file: 'nx.json', hash: 'nx.json.hash' },
|
||||||
'/root/package-lock.json': 'package-lock.json.hash',
|
{ file: 'package-lock.json', hash: 'package-lock.json.hash' },
|
||||||
'/root/package.json': 'package.json.hash',
|
{ file: 'package.json', hash: 'package.json.hash' },
|
||||||
'/root/pnpm-lock.yaml': 'pnpm-lock.yaml.hash',
|
{ file: 'pnpm-lock.yaml', hash: 'pnpm-lock.yaml.hash' },
|
||||||
'/root/tsconfig.base.json': tsConfigBaseJson,
|
{ file: 'tsconfig.base.json', hash: tsConfigBaseJson },
|
||||||
'/root/workspace.json': 'workspace.json.hash',
|
{ file: 'workspace.json', hash: 'workspace.json.hash' },
|
||||||
'/root/global1': 'global1.hash',
|
{ file: 'global1', hash: 'global1.hash' },
|
||||||
'/root/global2': 'global2.hash',
|
{ file: 'global2', hash: 'global2.hash' },
|
||||||
};
|
];
|
||||||
|
|
||||||
function createHashing(): any {
|
function createHashing(): any {
|
||||||
return {
|
return {
|
||||||
hashArray: (values: string[]) => values.join('|'),
|
hashArray: (values: string[]) => values.join('|'),
|
||||||
hashFile: (path: string) => hashes[path],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +98,7 @@ describe('Hasher', () => {
|
|||||||
dependencies: {
|
dependencies: {
|
||||||
parent: [],
|
parent: [],
|
||||||
},
|
},
|
||||||
|
allWorkspaceFiles,
|
||||||
},
|
},
|
||||||
{} as any,
|
{} as any,
|
||||||
{
|
{
|
||||||
@ -172,6 +171,7 @@ describe('Hasher', () => {
|
|||||||
dependencies: {
|
dependencies: {
|
||||||
parent: [{ source: 'parent', target: 'child', type: 'static' }],
|
parent: [{ source: 'parent', target: 'child', type: 'static' }],
|
||||||
},
|
},
|
||||||
|
allWorkspaceFiles,
|
||||||
},
|
},
|
||||||
{} as any,
|
{} as any,
|
||||||
{},
|
{},
|
||||||
@ -208,8 +208,8 @@ describe('Hasher', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
files: [
|
files: [
|
||||||
{ file: '/filea.ts', hash: 'a.hash' },
|
{ file: 'libs/parent/filea.ts', hash: 'a.hash' },
|
||||||
{ file: '/filea.spec.ts', hash: 'a.spec.hash' },
|
{ file: 'libs/parent/filea.spec.ts', hash: 'a.spec.hash' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -223,8 +223,8 @@ describe('Hasher', () => {
|
|||||||
},
|
},
|
||||||
targets: { build: {} },
|
targets: { build: {} },
|
||||||
files: [
|
files: [
|
||||||
{ file: '/fileb.ts', hash: 'b.hash' },
|
{ file: 'libs/child/fileb.ts', hash: 'b.hash' },
|
||||||
{ file: '/fileb.spec.ts', hash: 'b.spec.hash' },
|
{ file: 'libs/child/fileb.spec.ts', hash: 'b.spec.hash' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -232,10 +232,11 @@ describe('Hasher', () => {
|
|||||||
dependencies: {
|
dependencies: {
|
||||||
parent: [{ source: 'parent', target: 'child', type: 'static' }],
|
parent: [{ source: 'parent', target: 'child', type: 'static' }],
|
||||||
},
|
},
|
||||||
|
allWorkspaceFiles,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
namedInputs: {
|
namedInputs: {
|
||||||
prod: ['!**/*.spec.ts'],
|
prod: ['!{projectRoot}/**/*.spec.ts'],
|
||||||
},
|
},
|
||||||
} as any,
|
} as any,
|
||||||
{},
|
{},
|
||||||
@ -250,13 +251,13 @@ describe('Hasher', () => {
|
|||||||
|
|
||||||
expect(onlySourceNodes(hash.details.nodes)).toEqual({
|
expect(onlySourceNodes(hash.details.nodes)).toEqual({
|
||||||
'child:$filesets':
|
'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':
|
'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(
|
const hasher = new Hasher(
|
||||||
{
|
{
|
||||||
nodes: {
|
nodes: {
|
||||||
@ -269,8 +270,8 @@ describe('Hasher', () => {
|
|||||||
build: {},
|
build: {},
|
||||||
},
|
},
|
||||||
files: [
|
files: [
|
||||||
{ file: '/filea.ts', hash: 'a.hash' },
|
{ file: 'libs/parent/filea.ts', hash: 'a.hash' },
|
||||||
{ file: '/filea.spec.ts', hash: 'a.spec.hash' },
|
{ file: 'libs/parent/filea.spec.ts', hash: 'a.spec.hash' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -281,8 +282,8 @@ describe('Hasher', () => {
|
|||||||
root: 'libs/child',
|
root: 'libs/child',
|
||||||
targets: { build: {} },
|
targets: { build: {} },
|
||||||
files: [
|
files: [
|
||||||
{ file: '/fileb.ts', hash: 'b.hash' },
|
{ file: 'libs/child/fileb.ts', hash: 'b.hash' },
|
||||||
{ file: '/fileb.spec.ts', hash: 'b.spec.hash' },
|
{ file: 'libs/child/fileb.spec.ts', hash: 'b.spec.hash' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -290,10 +291,11 @@ describe('Hasher', () => {
|
|||||||
dependencies: {
|
dependencies: {
|
||||||
parent: [{ source: 'parent', target: 'child', type: 'static' }],
|
parent: [{ source: 'parent', target: 'child', type: 'static' }],
|
||||||
},
|
},
|
||||||
|
allWorkspaceFiles,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
namedInputs: {
|
namedInputs: {
|
||||||
prod: ['!**/*.spec.ts'],
|
prod: ['!{projectRoot}/**/*.spec.ts'],
|
||||||
},
|
},
|
||||||
targetDefaults: {
|
targetDefaults: {
|
||||||
build: {
|
build: {
|
||||||
@ -313,9 +315,9 @@ describe('Hasher', () => {
|
|||||||
|
|
||||||
expect(onlySourceNodes(hash.details.nodes)).toEqual({
|
expect(onlySourceNodes(hash.details.nodes)).toEqual({
|
||||||
'child:$filesets':
|
'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':
|
'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: {
|
dependencies: {
|
||||||
parent: [],
|
parent: [],
|
||||||
},
|
},
|
||||||
|
allWorkspaceFiles,
|
||||||
},
|
},
|
||||||
{ npmScope: 'nrwl' } as any,
|
{ npmScope: 'nrwl' } as any,
|
||||||
{
|
{
|
||||||
@ -393,6 +396,7 @@ describe('Hasher', () => {
|
|||||||
parent: [{ source: 'parent', target: 'child', type: 'static' }],
|
parent: [{ source: 'parent', target: 'child', type: 'static' }],
|
||||||
child: [{ source: 'child', target: 'parent', type: 'static' }],
|
child: [{ source: 'child', target: 'parent', type: 'static' }],
|
||||||
},
|
},
|
||||||
|
allWorkspaceFiles,
|
||||||
},
|
},
|
||||||
{} as any,
|
{} as any,
|
||||||
{},
|
{},
|
||||||
@ -455,6 +459,7 @@ describe('Hasher', () => {
|
|||||||
dependencies: {
|
dependencies: {
|
||||||
parent: [],
|
parent: [],
|
||||||
},
|
},
|
||||||
|
allWorkspaceFiles,
|
||||||
},
|
},
|
||||||
{} as any,
|
{} as any,
|
||||||
{
|
{
|
||||||
@ -494,16 +499,7 @@ describe('Hasher', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
dependencies: {},
|
dependencies: {},
|
||||||
allWorkspaceFiles: [
|
allWorkspaceFiles,
|
||||||
{
|
|
||||||
file: 'global1',
|
|
||||||
hash: 'hash1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
file: 'global2',
|
|
||||||
hash: 'hash2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
implicitDependencies: {
|
implicitDependencies: {
|
||||||
@ -554,6 +550,7 @@ describe('Hasher', () => {
|
|||||||
{ source: 'app', target: 'npm:react', type: DependencyType.static },
|
{ source: 'app', target: 'npm:react', type: DependencyType.static },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
allWorkspaceFiles,
|
||||||
},
|
},
|
||||||
{} as any,
|
{} as any,
|
||||||
{},
|
{},
|
||||||
@ -599,6 +596,7 @@ describe('Hasher', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
allWorkspaceFiles,
|
||||||
},
|
},
|
||||||
{} as any,
|
{} as any,
|
||||||
{},
|
{},
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import * as minimatch from 'minimatch';
|
import * as minimatch from 'minimatch';
|
||||||
import { existsSync } from 'fs';
|
|
||||||
import { getRootTsConfigFileName } from '../utils/typescript';
|
import { getRootTsConfigFileName } from '../utils/typescript';
|
||||||
import { defaultHashing, HashingImpl } from './hashing-impl';
|
import { defaultHashing, HashingImpl } from './hashing-impl';
|
||||||
import {
|
import {
|
||||||
@ -14,8 +13,6 @@ import { Task } from '../config/task-graph';
|
|||||||
import { readJsonFile } from '../utils/fileutils';
|
import { readJsonFile } from '../utils/fileutils';
|
||||||
import { InputDefinition } from '../config/workspace-json-project-json';
|
import { InputDefinition } from '../config/workspace-json-project-json';
|
||||||
import { getImportPath } from '../utils/path';
|
import { getImportPath } from '../utils/path';
|
||||||
import { workspaceRoot } from '../utils/workspace-root';
|
|
||||||
import { join } from 'path';
|
|
||||||
|
|
||||||
type ExpandedSelfInput =
|
type ExpandedSelfInput =
|
||||||
| { fileset: string }
|
| { fileset: string }
|
||||||
@ -183,7 +180,7 @@ export class Hasher {
|
|||||||
const DEFAULT_INPUTS = [
|
const DEFAULT_INPUTS = [
|
||||||
{
|
{
|
||||||
projects: 'self',
|
projects: 'self',
|
||||||
fileset: 'default',
|
fileset: '{projectRoot}/**/*',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
projects: 'dependencies',
|
projects: 'dependencies',
|
||||||
@ -283,13 +280,15 @@ class TaskHasher {
|
|||||||
task: Task,
|
task: Task,
|
||||||
projectNode: ProjectGraphProjectNode<any>
|
projectNode: ProjectGraphProjectNode<any>
|
||||||
): { depsInputs: { input: string }[]; selfInputs: ExpandedSelfInput[] } {
|
): { depsInputs: { input: string }[]; selfInputs: ExpandedSelfInput[] } {
|
||||||
|
const namedInputs = {
|
||||||
|
default: [{ fileset: '{projectRoot}/**/*' }],
|
||||||
|
...this.nxJson.namedInputs,
|
||||||
|
...projectNode.data.namedInputs,
|
||||||
|
};
|
||||||
if (task.target.target === '$input') {
|
if (task.target.target === '$input') {
|
||||||
return {
|
return {
|
||||||
depsInputs: [{ input: task.target.configuration }],
|
depsInputs: [{ input: task.target.configuration }],
|
||||||
selfInputs: expandNamedInput(task.target.configuration, {
|
selfInputs: expandNamedInput(task.target.configuration, namedInputs),
|
||||||
...this.nxJson.namedInputs,
|
|
||||||
...projectNode.data.namedInputs,
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const targetData = projectNode.data.targets[task.target.target];
|
const targetData = projectNode.data.targets[task.target.target];
|
||||||
@ -299,7 +298,7 @@ class TaskHasher {
|
|||||||
// task from TaskGraph can be added here
|
// task from TaskGraph can be added here
|
||||||
return splitInputsIntoSelfAndDependencies(
|
return splitInputsIntoSelfAndDependencies(
|
||||||
targetData.inputs || targetDefaults?.inputs || DEFAULT_INPUTS,
|
targetData.inputs || targetDefaults?.inputs || DEFAULT_INPUTS,
|
||||||
{ ...this.nxJson.namedInputs, ...projectNode.data.namedInputs }
|
namedInputs
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -340,25 +339,42 @@ class TaskHasher {
|
|||||||
.filter((r) => !!r['fileset'])
|
.filter((r) => !!r['fileset'])
|
||||||
.map((r) => r['fileset']);
|
.map((r) => r['fileset']);
|
||||||
|
|
||||||
const projectFilesets = filesets.filter(
|
const projectFilesets = [];
|
||||||
(r) => !r.startsWith('{workspaceRoot}')
|
const workspaceFilesets = [];
|
||||||
);
|
let invalidFileset = null;
|
||||||
|
|
||||||
const rootFilesets = filesets.filter((r) =>
|
|
||||||
r.startsWith('{workspaceRoot}/')
|
|
||||||
);
|
|
||||||
|
|
||||||
|
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([
|
return Promise.all([
|
||||||
this.hashTaskFileset(task, projectFilesets),
|
this.hashTaskFileset(task, projectFilesets),
|
||||||
...[
|
...[
|
||||||
...rootFilesets,
|
...workspaceFilesets,
|
||||||
...this.legacyFilesetInputs.map((r) => r.fileset),
|
...this.legacyFilesetInputs.map((r) => r.fileset),
|
||||||
].map((fileset) => this.hashRootFileset(fileset)),
|
].map((fileset) => this.hashRootFileset(fileset)),
|
||||||
...[...inputs, ...this.legacyRuntimeInputs]
|
...[...notFilesets, ...this.legacyRuntimeInputs].map((r) =>
|
||||||
.filter((r) => !r['fileset'])
|
r['runtime'] ? this.hashRuntime(r['runtime']) : this.hashEnv(r['env'])
|
||||||
.map((r) =>
|
),
|
||||||
r['runtime'] ? this.hashRuntime(r['runtime']) : this.hashEnv(r['env'])
|
|
||||||
),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,13 +388,14 @@ class TaskHasher {
|
|||||||
this.projectGraph.allWorkspaceFiles
|
this.projectGraph.allWorkspaceFiles
|
||||||
.filter((f) => minimatch(f.file, withoutWorkspaceRoot))
|
.filter((f) => minimatch(f.file, withoutWorkspaceRoot))
|
||||||
.forEach((f) => {
|
.forEach((f) => {
|
||||||
parts.push(this.hashing.hashFile(join(workspaceRoot, f.file)));
|
parts.push(f.hash);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (existsSync(join(workspaceRoot, withoutWorkspaceRoot))) {
|
const matchingFile = this.projectGraph.allWorkspaceFiles.find(
|
||||||
parts.push(
|
(t) => t.file === withoutWorkspaceRoot
|
||||||
this.hashing.hashFile(join(workspaceRoot, withoutWorkspaceRoot))
|
);
|
||||||
);
|
if (matchingFile) {
|
||||||
|
parts.push(matchingFile.hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const value = this.hashing.hashArray(parts);
|
const value = this.hashing.hashArray(parts);
|
||||||
@ -399,7 +416,14 @@ class TaskHasher {
|
|||||||
if (!this.filesetHashes[mapKey]) {
|
if (!this.filesetHashes[mapKey]) {
|
||||||
this.filesetHashes[mapKey] = new Promise(async (res) => {
|
this.filesetHashes[mapKey] = new Promise(async (res) => {
|
||||||
const p = this.projectGraph.nodes[task.target.project];
|
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 fileNames = filteredFiles.map((f) => f.file);
|
||||||
const values = filteredFiles.map((f) => f.hash);
|
const values = filteredFiles.map((f) => f.hash);
|
||||||
|
|
||||||
@ -450,9 +474,12 @@ class TaskHasher {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private filterFiles(files: FileData[], patterns: string[]) {
|
private filterFiles(
|
||||||
patterns = patterns.filter((p) => p !== 'default');
|
projectRoot: string,
|
||||||
if (patterns.length === 0) return files;
|
files: FileData[],
|
||||||
|
patterns: string[]
|
||||||
|
) {
|
||||||
|
if (patterns.indexOf(`${projectRoot}/**/*`) > -1) return files;
|
||||||
return files.filter(
|
return files.filter(
|
||||||
(f) => !!patterns.find((pattern) => minimatch(f.file, pattern))
|
(f) => !!patterns.find((pattern) => minimatch(f.file, pattern))
|
||||||
);
|
);
|
||||||
@ -545,7 +572,6 @@ export function expandNamedInput(
|
|||||||
input: string,
|
input: string,
|
||||||
namedInputs: { [inputName: string]: (InputDefinition | string)[] }
|
namedInputs: { [inputName: string]: (InputDefinition | string)[] }
|
||||||
): ExpandedSelfInput[] {
|
): ExpandedSelfInput[] {
|
||||||
if (input === 'default') return [{ fileset: 'default' }];
|
|
||||||
namedInputs ||= {};
|
namedInputs ||= {};
|
||||||
if (!namedInputs[input]) throw new Error(`Input '${input}' is not defined`);
|
if (!namedInputs[input]) throw new Error(`Input '${input}' is not defined`);
|
||||||
return expandSelfInputs(namedInputs[input], namedInputs);
|
return expandSelfInputs(namedInputs[input], namedInputs);
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { WholeFileChange } from '../../file-utils';
|
|||||||
import {
|
import {
|
||||||
getTouchedProjects,
|
getTouchedProjects,
|
||||||
getImplicitlyTouchedProjects,
|
getImplicitlyTouchedProjects,
|
||||||
|
extractGlobalFilesFromInputs,
|
||||||
} from './workspace-projects';
|
} from './workspace-projects';
|
||||||
|
|
||||||
function getFileChanges(files: string[]) {
|
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(
|
function buildProjectGraphNodes(
|
||||||
projects: Record<string, ProjectConfiguration>
|
projects: Record<string, ProjectConfiguration>
|
||||||
): ProjectGraph['nodes'] {
|
): ProjectGraph['nodes'] {
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import * as minimatch from 'minimatch';
|
import * as minimatch from 'minimatch';
|
||||||
import { TouchedProjectLocator } from '../affected-project-graph-models';
|
import { TouchedProjectLocator } from '../affected-project-graph-models';
|
||||||
|
import { NxJsonConfiguration } from '../../../config/nx-json';
|
||||||
|
import { ProjectGraphProjectNode } from '../../../config/project-graph';
|
||||||
|
|
||||||
export const getTouchedProjects: TouchedProjectLocator = (
|
export const getTouchedProjects: TouchedProjectLocator = (
|
||||||
touchedFiles,
|
touchedFiles,
|
||||||
@ -26,18 +28,22 @@ export const getTouchedProjects: TouchedProjectLocator = (
|
|||||||
|
|
||||||
export const getImplicitlyTouchedProjects: TouchedProjectLocator = (
|
export const getImplicitlyTouchedProjects: TouchedProjectLocator = (
|
||||||
fileChanges,
|
fileChanges,
|
||||||
workspaceJson,
|
projectGraphNodes,
|
||||||
nxJson
|
nxJson
|
||||||
): string[] => {
|
): string[] => {
|
||||||
if (!nxJson.implicitDependencies) {
|
const implicits = { ...nxJson.implicitDependencies };
|
||||||
return [];
|
const globalFiles = [
|
||||||
}
|
...extractGlobalFilesFromInputs(nxJson, projectGraphNodes),
|
||||||
|
'nx.json',
|
||||||
|
'package.json',
|
||||||
|
];
|
||||||
|
globalFiles.forEach((file) => {
|
||||||
|
implicits[file] = '*' as any;
|
||||||
|
});
|
||||||
|
|
||||||
const touched = new Set<string>();
|
const touched = new Set<string>();
|
||||||
|
|
||||||
for (const [pattern, projects] of Object.entries(
|
for (const [pattern, projects] of Object.entries(implicits)) {
|
||||||
nxJson.implicitDependencies
|
|
||||||
)) {
|
|
||||||
const implicitDependencyWasChanged = fileChanges.some((f) =>
|
const implicitDependencyWasChanged = fileChanges.some((f) =>
|
||||||
minimatch(f.file, pattern)
|
minimatch(f.file, pattern)
|
||||||
);
|
);
|
||||||
@ -53,3 +59,49 @@ export const getImplicitlyTouchedProjects: TouchedProjectLocator = (
|
|||||||
|
|
||||||
return Array.from(touched);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { reverse, withDeps, filterNodes } from './operators';
|
import { reverse, filterNodes } from './operators';
|
||||||
import {
|
import {
|
||||||
DependencyType,
|
DependencyType,
|
||||||
ProjectGraph,
|
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', () => {
|
describe('filterNodes', () => {
|
||||||
it('filters out nodes based on predicate', () => {
|
it('filters out nodes based on predicate', () => {
|
||||||
const result = filterNodes((n) => n.type === 'app')(graph);
|
const result = filterNodes((n) => n.type === 'app')(graph);
|
||||||
|
|||||||
@ -219,7 +219,12 @@ function interpolateOverrides<T = any>(
|
|||||||
Object.entries(interpolatedArgs).forEach(([name, value]) => {
|
Object.entries(interpolatedArgs).forEach(([name, value]) => {
|
||||||
interpolatedArgs[name] =
|
interpolatedArgs[name] =
|
||||||
typeof value === 'string'
|
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;
|
: value;
|
||||||
});
|
});
|
||||||
return interpolatedArgs;
|
return interpolatedArgs;
|
||||||
|
|||||||
@ -55,10 +55,6 @@ export const defaultTasksRunner: TasksRunner<
|
|||||||
options.lifeCycle.startCommand();
|
options.lifeCycle.startCommand();
|
||||||
try {
|
try {
|
||||||
return await runAllTasks(tasks, options, context);
|
return await runAllTasks(tasks, options, context);
|
||||||
} catch (e) {
|
|
||||||
console.error('Unexpected error:');
|
|
||||||
console.error(e);
|
|
||||||
process.exit(1);
|
|
||||||
} finally {
|
} finally {
|
||||||
options.lifeCycle.endCommand();
|
options.lifeCycle.endCommand();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,8 @@ import {
|
|||||||
import { Task } from '../config/task-graph';
|
import { Task } from '../config/task-graph';
|
||||||
import { createTaskGraph } from './create-task-graph';
|
import { createTaskGraph } from './create-task-graph';
|
||||||
import { findCycle, makeAcyclic } from './task-graph-utils';
|
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(
|
async function getTerminalOutputLifeCycle(
|
||||||
initiatingProject: string,
|
initiatingProject: string,
|
||||||
@ -90,81 +91,80 @@ export async function runCommand(
|
|||||||
initiatingProject: string | null,
|
initiatingProject: string | null,
|
||||||
extraTargetDependencies: Record<string, (TargetDependencyConfig | string)[]>
|
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(
|
const defaultDependencyConfigs = mergeTargetDependencies(
|
||||||
nxJson.targetDefaults,
|
nxJson.targetDefaults,
|
||||||
extraTargetDependencies
|
extraTargetDependencies
|
||||||
);
|
);
|
||||||
const projectNames = projectsToRun.map((t) => t.name);
|
const projectNames = projectsToRun.map((t) => t.name);
|
||||||
const taskGraph = createTaskGraph(
|
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,
|
|
||||||
projectGraph,
|
projectGraph,
|
||||||
nxJson,
|
defaultDependencyConfigs,
|
||||||
nxArgs,
|
projectNames,
|
||||||
taskGraph,
|
[nxArgs.target],
|
||||||
}
|
nxArgs.configuration,
|
||||||
);
|
overrides
|
||||||
|
);
|
||||||
|
|
||||||
let anyFailures;
|
const cycle = findCycle(taskGraph);
|
||||||
try {
|
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) {
|
if ((promiseOrObservable as any).subscribe) {
|
||||||
anyFailures = await anyFailuresInObservable(promiseOrObservable);
|
anyFailures = await anyFailuresInObservable(promiseOrObservable);
|
||||||
} else {
|
} else {
|
||||||
@ -172,18 +172,11 @@ export async function runCommand(
|
|||||||
anyFailures = await anyFailuresInPromise(promiseOrObservable as any);
|
anyFailures = await anyFailuresInPromise(promiseOrObservable as any);
|
||||||
}
|
}
|
||||||
await renderIsDone;
|
await renderIsDone;
|
||||||
} catch (e) {
|
return anyFailures ? 1 : 0;
|
||||||
output.error({
|
});
|
||||||
title: 'Unhandled error in task executor',
|
|
||||||
});
|
|
||||||
console.error(e);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// fix for https://github.com/nrwl/nx/issues/1666
|
// fix for https://github.com/nrwl/nx/issues/1666
|
||||||
if (process.stdin['unref']) (process.stdin as any).unref();
|
if (process.stdin['unref']) (process.stdin as any).unref();
|
||||||
|
process.exit(status);
|
||||||
process.exit(anyFailures ? 1 : 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeTargetDependencies(
|
function mergeTargetDependencies(
|
||||||
@ -261,10 +254,7 @@ export function getRunner(
|
|||||||
let runner = nxArgs.runner;
|
let runner = nxArgs.runner;
|
||||||
runner = runner || 'default';
|
runner = runner || 'default';
|
||||||
if (!nxJson.tasksRunnerOptions) {
|
if (!nxJson.tasksRunnerOptions) {
|
||||||
output.error({
|
throw new Error(`Could not find any runner configurations in nx.json`);
|
||||||
title: `Could not find any runner configurations in nx.json`,
|
|
||||||
});
|
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
if (nxJson.tasksRunnerOptions[runner]) {
|
if (nxJson.tasksRunnerOptions[runner]) {
|
||||||
let modulePath: string = nxJson.tasksRunnerOptions[runner].runner;
|
let modulePath: string = nxJson.tasksRunnerOptions[runner].runner;
|
||||||
@ -292,9 +282,6 @@ export function getRunner(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
output.error({
|
throw new Error(`Could not find runner configuration for ${runner}`);
|
||||||
title: `Could not find runner configuration for ${runner}`,
|
|
||||||
});
|
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,7 +68,7 @@ describe('utils', () => {
|
|||||||
getOutputsForTargetAndConfiguration(
|
getOutputsForTargetAndConfiguration(
|
||||||
task,
|
task,
|
||||||
getNode({
|
getNode({
|
||||||
outputs: ['{project.root}/sub', 'two'],
|
outputs: ['{projectRoot}/sub', 'two'],
|
||||||
options: {
|
options: {
|
||||||
myVar: 'one',
|
myVar: 'one',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -97,8 +97,11 @@ export function getOutputsForTargetAndConfiguration(
|
|||||||
return targets.outputs
|
return targets.outputs
|
||||||
.map((output: string) => {
|
.map((output: string) => {
|
||||||
const interpolated = interpolate(output, {
|
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,
|
options,
|
||||||
project: { ...node.data, name: node.name },
|
|
||||||
});
|
});
|
||||||
return isRelativePath(interpolated)
|
return isRelativePath(interpolated)
|
||||||
? joinPathFragments(node.data.root, interpolated)
|
? joinPathFragments(node.data.root, interpolated)
|
||||||
|
|||||||
@ -76,7 +76,6 @@ const runOne: string[] = [
|
|||||||
'exclude',
|
'exclude',
|
||||||
'onlyFailed',
|
'onlyFailed',
|
||||||
'help',
|
'help',
|
||||||
'withDeps',
|
|
||||||
'skipNxCache',
|
'skipNxCache',
|
||||||
'scan',
|
'scan',
|
||||||
'outputStyle',
|
'outputStyle',
|
||||||
@ -120,7 +119,6 @@ export interface NxArgs {
|
|||||||
help?: boolean;
|
help?: boolean;
|
||||||
version?: boolean;
|
version?: boolean;
|
||||||
plain?: boolean;
|
plain?: boolean;
|
||||||
withDeps?: boolean;
|
|
||||||
projects?: string[];
|
projects?: string[];
|
||||||
select?: string;
|
select?: string;
|
||||||
skipNxCache?: boolean;
|
skipNxCache?: boolean;
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import {
|
|||||||
TargetConfiguration,
|
TargetConfiguration,
|
||||||
ProjectsConfigurations,
|
ProjectsConfigurations,
|
||||||
} from '../config/workspace-json-project-json';
|
} from '../config/workspace-json-project-json';
|
||||||
import { execSync } from 'child_process';
|
import { output } from './output';
|
||||||
|
|
||||||
type PropertyDescription = {
|
type PropertyDescription = {
|
||||||
type?: string | string[];
|
type?: string | string[];
|
||||||
@ -77,16 +77,21 @@ export async function handleErrors(isVerbose: boolean, fn: Function) {
|
|||||||
return await fn();
|
return await fn();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
err ??= new Error('Unknown error caught');
|
err ??= new Error('Unknown error caught');
|
||||||
|
|
||||||
if (err.constructor.name === 'UnsuccessfulWorkflowExecution') {
|
if (err.constructor.name === 'UnsuccessfulWorkflowExecution') {
|
||||||
logger.error('The generator workflow failed. See above.');
|
logger.error('The generator workflow failed. See above.');
|
||||||
} else if (err.message) {
|
|
||||||
logger.error(err.message);
|
|
||||||
} else {
|
} else {
|
||||||
logger.error(err);
|
const lines = (err.message ? err.message : err.toString()).split('\n');
|
||||||
}
|
const bodyLines = lines.slice(1);
|
||||||
if (isVerbose && err.stack) {
|
if (err.stack && !isVerbose) {
|
||||||
logger.info(err.stack);
|
bodyLines.push('Pass --verbose to see the stacktrace.');
|
||||||
|
}
|
||||||
|
output.error({
|
||||||
|
title: lines[0],
|
||||||
|
bodyLines,
|
||||||
|
});
|
||||||
|
if (err.stack && isVerbose) {
|
||||||
|
logger.info(err.stack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,9 +47,6 @@ function getHttpServerArgs(options: Schema) {
|
|||||||
|
|
||||||
function getBuildTargetCommand(options: Schema) {
|
function getBuildTargetCommand(options: Schema) {
|
||||||
const cmd = ['nx', 'run', options.buildTarget];
|
const cmd = ['nx', 'run', options.buildTarget];
|
||||||
if (options.withDeps) {
|
|
||||||
cmd.push(`--with-deps`);
|
|
||||||
}
|
|
||||||
if (options.parallel) {
|
if (options.parallel) {
|
||||||
cmd.push(`--parallel`);
|
cmd.push(`--parallel`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -212,20 +212,13 @@ export function checkDependentProjectsHaveBeenBuilt(
|
|||||||
targetName,
|
targetName,
|
||||||
projectDependencies
|
projectDependencies
|
||||||
);
|
);
|
||||||
if (missing.length === projectDependencies.length && missing.length > 0) {
|
if (missing.length > 0) {
|
||||||
console.error(stripIndents`
|
console.error(stripIndents`
|
||||||
It looks like all of ${projectName}'s dependencies have not been built yet:
|
It looks like all of ${projectName}'s dependencies have not been built yet:
|
||||||
${missing.map((x) => ` - ${x.node.name}`).join('\n')}
|
${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),
|
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 angular.json/workspace.json record or project.json (https://nx.dev/configuration/packagejson#dependson)
|
or "dependsOn" configured in ${projectName}'s project.json (https://nx.dev/configuration/projectjson#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
|
|
||||||
`);
|
`);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user