feat(core): merge more target options from targetDefaults (#12435)
Fixes https://github.com/nrwl/nx/issues/12433
This commit is contained in:
parent
66a26583da
commit
c783ac5e9a
@ -113,6 +113,13 @@ In this case Nx will use the right `production` input for each project.
|
|||||||
|
|
||||||
### Target Defaults
|
### Target Defaults
|
||||||
|
|
||||||
|
Target defaults provide ways to set common options for a particular target in your workspace. When building your project's configuration, we merge it with up to 1 default from this map. For a given target, we look at its name and its executor. We then check target defaults for any of the following combinations:
|
||||||
|
|
||||||
|
- `` `${executor}` ``
|
||||||
|
- `` `${targetName}` ``
|
||||||
|
|
||||||
|
Whichever of these we find first, we use as the base for that target's configuration. Some common scenarios for this follow.
|
||||||
|
|
||||||
Targets can depend on other targets. A common scenario is having to build dependencies of a project first before
|
Targets can depend on other targets. A common scenario is having to build dependencies of a project first before
|
||||||
building the project. The `dependsOn` property in `project.json` can be used to define the list of dependencies of an
|
building the project. The `dependsOn` property in `project.json` can be used to define the list of dependencies of an
|
||||||
individual target.
|
individual target.
|
||||||
@ -149,6 +156,35 @@ Another target default you can configure is `outputs`:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When defining any options or configurations inside of a target default, you may use the `{workspaceRoot}` and `{projectRoot}` tokens. This is useful for defining things like the outputPath or tsconfig for many build targets.
|
||||||
|
|
||||||
|
```json {% fileName="nx.json" %}
|
||||||
|
{
|
||||||
|
"targetDefaults": {
|
||||||
|
"@nrwl/js:tsc": {
|
||||||
|
"options": {
|
||||||
|
"main": "{projectRoot}/src/index.ts"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"prod": {
|
||||||
|
"tsconfig": "{projectRoot}/tsconfig.prod.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inputs": ["prod"],
|
||||||
|
"outputs": ["{workspaceRoot}/{projectRoot}"]
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"inputs": ["prod"],
|
||||||
|
"outputs": ["{workspaceRoot}/{projectRoot}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{% callout type="note" title="Target Default Priority" %}
|
||||||
|
Note that the inputs and outputs are respecified on the @nrwl/js:tsc default configuration. This is **required**, as when reading target defaults Nx will only ever look at one key. If there is a default configuration based on the executor used, it will be read first. If not, Nx will fall back to looking at the configuration based on target name. For instance, running `nx build project` will read the options from `targetDefaults[@nrwl/js:tsc]` if the target configuration for build uses the @nrwl/js:tsc executor. It **would not** read any of the configuration from the `build` target default configuration unless the executor does not match.
|
||||||
|
{% /callout %}
|
||||||
|
|
||||||
### Generators
|
### Generators
|
||||||
|
|
||||||
Default generator options are configured in `nx.json` as well. For instance, the following tells Nx to always
|
Default generator options are configured in `nx.json` as well. For instance, the following tells Nx to always
|
||||||
@ -174,7 +210,7 @@ named "default" is used by default. Specify a different one like this `nx run-ma
|
|||||||
Tasks runners can accept different options. The following are the options supported
|
Tasks runners can accept different options. The following are the options supported
|
||||||
by `"nx/tasks-runners/default"` and `"@nrwl/nx-cloud"`.
|
by `"nx/tasks-runners/default"` and `"@nrwl/nx-cloud"`.
|
||||||
|
|
||||||
| Property | Descrtipion |
|
| Property | Description |
|
||||||
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| cacheableOperations | defines the list of targets/operations that are cached by Nx |
|
| cacheableOperations | defines the list of targets/operations that are cached by Nx |
|
||||||
| parallel | defines the max number of targets ran in parallel (in older versions of Nx you had to pass `--parallel --maxParallel=3` instead of `--parallel=3`) |
|
| parallel | defines the max number of targets ran in parallel (in older versions of Nx you had to pass `--parallel --maxParallel=3` instead of `--parallel=3`) |
|
||||||
|
|||||||
@ -254,6 +254,69 @@ describe('Nx Running Tests', () => {
|
|||||||
);
|
);
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
|
describe('target defaults + executor specifications', () => {
|
||||||
|
it('should be able to run targets with unspecified executor given an appropriate targetDefaults entry', () => {
|
||||||
|
const target = uniq('target');
|
||||||
|
const lib = uniq('lib');
|
||||||
|
|
||||||
|
updateJson('nx.json', (nxJson) => {
|
||||||
|
nxJson.targetDefaults ??= {};
|
||||||
|
nxJson.targetDefaults[target] = {
|
||||||
|
executor: 'nx:run-commands',
|
||||||
|
options: {
|
||||||
|
command: `echo Hello from ${target}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return nxJson;
|
||||||
|
});
|
||||||
|
|
||||||
|
updateFile(
|
||||||
|
`libs/${lib}/project.json`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: lib,
|
||||||
|
targets: {
|
||||||
|
[target]: {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(runCLI(`${target} ${lib} --verbose`)).toContain(
|
||||||
|
`Hello from ${target}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to pull options from targetDefaults based on executor', () => {
|
||||||
|
const target = uniq('target');
|
||||||
|
const lib = uniq('lib');
|
||||||
|
|
||||||
|
updateJson('nx.json', (nxJson) => {
|
||||||
|
nxJson.targetDefaults ??= {};
|
||||||
|
nxJson.targetDefaults[`nx:run-commands`] = {
|
||||||
|
options: {
|
||||||
|
command: `echo Hello from ${target}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return nxJson;
|
||||||
|
});
|
||||||
|
|
||||||
|
updateFile(
|
||||||
|
`libs/${lib}/project.json`,
|
||||||
|
JSON.stringify({
|
||||||
|
name: lib,
|
||||||
|
targets: {
|
||||||
|
[target]: {
|
||||||
|
executor: 'nx:run-commands',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(runCLI(`${target} ${lib} --verbose`)).toContain(
|
||||||
|
`Hello from ${target}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('target dependencies', () => {
|
describe('target dependencies', () => {
|
||||||
let myapp;
|
let myapp;
|
||||||
let mylib1;
|
let mylib1;
|
||||||
|
|||||||
@ -209,6 +209,26 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Target defaults",
|
"description": "Target defaults",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"executor": {
|
||||||
|
"description": "The function that Nx will invoke when you run this target",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "provides extra sets of values that will be merged into the options map",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"$ref": "#/definitions/inputs"
|
"$ref": "#/definitions/inputs"
|
||||||
},
|
},
|
||||||
@ -243,12 +263,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"outputs": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { PackageManager } from '../utils/package-manager';
|
import { PackageManager } from '../utils/package-manager';
|
||||||
import {
|
import {
|
||||||
InputDefinition,
|
InputDefinition,
|
||||||
|
TargetConfiguration,
|
||||||
TargetDependencyConfig,
|
TargetDependencyConfig,
|
||||||
} from './workspace-json-project-json';
|
} from './workspace-json-project-json';
|
||||||
|
|
||||||
@ -19,14 +20,7 @@ export interface NxAffectedConfig {
|
|||||||
defaultBase?: string;
|
defaultBase?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TargetDefaults = Record<
|
export type TargetDefaults = Record<string, Partial<TargetConfiguration>>;
|
||||||
string,
|
|
||||||
{
|
|
||||||
outputs?: string[];
|
|
||||||
dependsOn?: (TargetDependencyConfig | string)[];
|
|
||||||
inputs?: (InputDefinition | string)[];
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type TargetDependencies = Record<
|
export type TargetDependencies = Record<
|
||||||
string,
|
string,
|
||||||
|
|||||||
@ -1,8 +1,14 @@
|
|||||||
import { toProjectName, Workspaces } from './workspaces';
|
import {
|
||||||
|
mergeTargetConfigurations,
|
||||||
|
readTargetDefaultsForTarget,
|
||||||
|
toProjectName,
|
||||||
|
Workspaces,
|
||||||
|
} from './workspaces';
|
||||||
import { NxJsonConfiguration } from './nx-json';
|
import { NxJsonConfiguration } from './nx-json';
|
||||||
import { vol } from 'memfs';
|
import { vol } from 'memfs';
|
||||||
|
|
||||||
import * as fastGlob from 'fast-glob';
|
import * as fastGlob from 'fast-glob';
|
||||||
|
import { TargetConfiguration } from './workspace-json-project-json';
|
||||||
|
|
||||||
jest.mock('fs', () => require('memfs').fs);
|
jest.mock('fs', () => require('memfs').fs);
|
||||||
|
|
||||||
@ -138,4 +144,316 @@ describe('Workspaces', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('target defaults', () => {
|
||||||
|
const targetDefaults = {
|
||||||
|
'nx:run-commands': {
|
||||||
|
options: {
|
||||||
|
key: 'default-value-for-executor',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
options: {
|
||||||
|
key: 'default-value-for-targetname',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should prefer executor key', () => {
|
||||||
|
expect(
|
||||||
|
readTargetDefaultsForTarget(
|
||||||
|
'other-target',
|
||||||
|
targetDefaults,
|
||||||
|
'nx:run-commands'
|
||||||
|
).options['key']
|
||||||
|
).toEqual('default-value-for-executor');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fallback to target key', () => {
|
||||||
|
expect(
|
||||||
|
readTargetDefaultsForTarget('build', targetDefaults, 'other-executor')
|
||||||
|
.options['key']
|
||||||
|
).toEqual('default-value-for-targetname');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if not found', () => {
|
||||||
|
expect(
|
||||||
|
readTargetDefaultsForTarget(
|
||||||
|
'other-target',
|
||||||
|
targetDefaults,
|
||||||
|
'other-executor'
|
||||||
|
)
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('options', () => {
|
||||||
|
it('should merge if executor matches', () => {
|
||||||
|
expect(
|
||||||
|
mergeTargetConfigurations(
|
||||||
|
{
|
||||||
|
root: '.',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'target',
|
||||||
|
options: {
|
||||||
|
a: 'project-value-a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'build',
|
||||||
|
{
|
||||||
|
executor: 'target',
|
||||||
|
options: {
|
||||||
|
a: 'default-value-a',
|
||||||
|
b: 'default-value-b',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
).options
|
||||||
|
).toEqual({ a: 'project-value-a', b: 'default-value-b' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge if executor is only provided on the project', () => {
|
||||||
|
expect(
|
||||||
|
mergeTargetConfigurations(
|
||||||
|
{
|
||||||
|
root: '.',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'target',
|
||||||
|
options: {
|
||||||
|
a: 'project-value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'build',
|
||||||
|
{
|
||||||
|
options: {
|
||||||
|
a: 'default-value',
|
||||||
|
b: 'default-value',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
).options
|
||||||
|
).toEqual({ a: 'project-value', b: 'default-value' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge if executor is only provided in the defaults', () => {
|
||||||
|
expect(
|
||||||
|
mergeTargetConfigurations(
|
||||||
|
{
|
||||||
|
root: '.',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
options: {
|
||||||
|
a: 'project-value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'build',
|
||||||
|
{
|
||||||
|
executor: 'target',
|
||||||
|
options: {
|
||||||
|
a: 'default-value',
|
||||||
|
b: 'default-value',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
).options
|
||||||
|
).toEqual({ a: 'project-value', b: 'default-value' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not merge if executor is different', () => {
|
||||||
|
expect(
|
||||||
|
mergeTargetConfigurations(
|
||||||
|
{
|
||||||
|
root: '',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'other',
|
||||||
|
options: {
|
||||||
|
a: 'project-value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'build',
|
||||||
|
{
|
||||||
|
executor: 'default-executor',
|
||||||
|
options: {
|
||||||
|
b: 'default-value',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
).options
|
||||||
|
).toEqual({ a: 'project-value' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve workspaceRoot and projectRoot tokens', () => {
|
||||||
|
expect(
|
||||||
|
mergeTargetConfigurations(
|
||||||
|
{
|
||||||
|
root: 'my/project',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
options: {
|
||||||
|
a: '{workspaceRoot}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'build',
|
||||||
|
{
|
||||||
|
executor: 'target',
|
||||||
|
options: {
|
||||||
|
b: '{workspaceRoot}/dist/{projectRoot}',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
).options
|
||||||
|
).toEqual({ a: '{workspaceRoot}', b: 'dist/my/project' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('configurations', () => {
|
||||||
|
const projectConfigurations: TargetConfiguration['configurations'] = {
|
||||||
|
dev: {
|
||||||
|
foo: 'project-value-foo',
|
||||||
|
},
|
||||||
|
prod: {
|
||||||
|
bar: 'project-value-bar',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultConfigurations: TargetConfiguration['configurations'] = {
|
||||||
|
dev: {
|
||||||
|
foo: 'default-value-foo',
|
||||||
|
other: 'default-value-other',
|
||||||
|
},
|
||||||
|
baz: {
|
||||||
|
x: 'default-value-x',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const merged: TargetConfiguration['configurations'] = {
|
||||||
|
dev: {
|
||||||
|
foo: projectConfigurations.dev.foo,
|
||||||
|
other: defaultConfigurations.dev.other,
|
||||||
|
},
|
||||||
|
prod: { bar: projectConfigurations.prod.bar },
|
||||||
|
baz: { x: defaultConfigurations.baz.x },
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should merge configurations if executor matches', () => {
|
||||||
|
expect(
|
||||||
|
mergeTargetConfigurations(
|
||||||
|
{
|
||||||
|
root: '.',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'target',
|
||||||
|
configurations: projectConfigurations,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'build',
|
||||||
|
{
|
||||||
|
executor: 'target',
|
||||||
|
configurations: defaultConfigurations,
|
||||||
|
}
|
||||||
|
).configurations
|
||||||
|
).toEqual(merged);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge if executor is only provided on the project', () => {
|
||||||
|
expect(
|
||||||
|
mergeTargetConfigurations(
|
||||||
|
{
|
||||||
|
root: '.',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'target',
|
||||||
|
configurations: projectConfigurations,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'build',
|
||||||
|
{
|
||||||
|
configurations: defaultConfigurations,
|
||||||
|
}
|
||||||
|
).configurations
|
||||||
|
).toEqual(merged);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge if executor is only provided in the defaults', () => {
|
||||||
|
expect(
|
||||||
|
mergeTargetConfigurations(
|
||||||
|
{
|
||||||
|
root: '.',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
configurations: projectConfigurations,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'build',
|
||||||
|
{
|
||||||
|
executor: 'target',
|
||||||
|
configurations: defaultConfigurations,
|
||||||
|
}
|
||||||
|
).configurations
|
||||||
|
).toEqual(merged);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not merge if executor doesnt match', () => {
|
||||||
|
expect(
|
||||||
|
mergeTargetConfigurations(
|
||||||
|
{
|
||||||
|
root: '',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'other',
|
||||||
|
configurations: projectConfigurations,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'build',
|
||||||
|
{
|
||||||
|
executor: 'target',
|
||||||
|
configurations: defaultConfigurations,
|
||||||
|
}
|
||||||
|
).configurations
|
||||||
|
).toEqual(projectConfigurations);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve workspaceRoot and projectRoot tokens', () => {
|
||||||
|
expect(
|
||||||
|
mergeTargetConfigurations(
|
||||||
|
{
|
||||||
|
root: 'my/project',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
configurations: {
|
||||||
|
dev: {
|
||||||
|
a: '{workspaceRoot}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'build',
|
||||||
|
{
|
||||||
|
executor: 'target',
|
||||||
|
configurations: {
|
||||||
|
prod: {
|
||||||
|
a: '{workspaceRoot}/dist/{projectRoot}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
).configurations
|
||||||
|
).toEqual({
|
||||||
|
dev: { a: '{workspaceRoot}' },
|
||||||
|
prod: { a: 'dist/my/project' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,14 +7,15 @@ import { performance } from 'perf_hooks';
|
|||||||
|
|
||||||
import { workspaceRoot } from '../utils/workspace-root';
|
import { workspaceRoot } from '../utils/workspace-root';
|
||||||
import { readJsonFile } from '../utils/fileutils';
|
import { readJsonFile } from '../utils/fileutils';
|
||||||
import { logger, NX_PREFIX } from '../utils/logger';
|
import { logger, NX_PREFIX, stripIndent } from '../utils/logger';
|
||||||
import { loadNxPlugins, readPluginPackageJson } from '../utils/nx-plugin';
|
import { loadNxPlugins, readPluginPackageJson } from '../utils/nx-plugin';
|
||||||
import * as yaml from 'js-yaml';
|
import * as yaml from 'js-yaml';
|
||||||
|
|
||||||
import type { NxJsonConfiguration } from './nx-json';
|
import type { NxJsonConfiguration, TargetDefaults } from './nx-json';
|
||||||
import {
|
import {
|
||||||
ProjectConfiguration,
|
ProjectConfiguration,
|
||||||
ProjectsConfigurations,
|
ProjectsConfigurations,
|
||||||
|
TargetConfiguration,
|
||||||
} from './workspace-json-project-json';
|
} from './workspace-json-project-json';
|
||||||
import {
|
import {
|
||||||
CustomHasher,
|
CustomHasher,
|
||||||
@ -140,16 +141,19 @@ export class Workspaces {
|
|||||||
for (const proj of Object.values(config.projects)) {
|
for (const proj of Object.values(config.projects)) {
|
||||||
if (proj.targets) {
|
if (proj.targets) {
|
||||||
for (const targetName of Object.keys(proj.targets)) {
|
for (const targetName of Object.keys(proj.targets)) {
|
||||||
if (nxJson.targetDefaults[targetName]) {
|
|
||||||
const projectTargetDefinition = proj.targets[targetName];
|
const projectTargetDefinition = proj.targets[targetName];
|
||||||
if (!projectTargetDefinition.outputs) {
|
const defaults = readTargetDefaultsForTarget(
|
||||||
projectTargetDefinition.outputs =
|
targetName,
|
||||||
nxJson.targetDefaults[targetName].outputs;
|
nxJson.targetDefaults,
|
||||||
}
|
projectTargetDefinition.executor
|
||||||
if (!projectTargetDefinition.dependsOn) {
|
);
|
||||||
projectTargetDefinition.dependsOn =
|
|
||||||
nxJson.targetDefaults[targetName].dependsOn;
|
if (defaults) {
|
||||||
}
|
proj.targets[targetName] = mergeTargetConfigurations(
|
||||||
|
proj,
|
||||||
|
targetName,
|
||||||
|
defaults
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -928,6 +932,125 @@ export function buildWorkspaceConfigurationFromGlobs(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mergeTargetConfigurations(
|
||||||
|
projectConfiguration: ProjectConfiguration,
|
||||||
|
target: string,
|
||||||
|
targetDefaults: TargetDefaults[string]
|
||||||
|
): TargetConfiguration {
|
||||||
|
const { targets, root } = projectConfiguration;
|
||||||
|
const targetConfiguration = targets?.[target];
|
||||||
|
|
||||||
|
if (!targetConfiguration) {
|
||||||
|
throw new Error(
|
||||||
|
`Attempted to merge targetDefaults for ${projectConfiguration.name}.${target}, which doesn't exist.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
configurations: defaultConfigurations,
|
||||||
|
options: defaultOptions,
|
||||||
|
...defaults
|
||||||
|
} = targetDefaults;
|
||||||
|
const result = {
|
||||||
|
...defaults,
|
||||||
|
...targetConfiguration,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Target is "compatible", e.g. executor is defined only once or is the same
|
||||||
|
// in both places. This means that it is likely safe to merge options
|
||||||
|
if (
|
||||||
|
!targetDefaults.executor ||
|
||||||
|
!targetConfiguration.executor ||
|
||||||
|
targetDefaults.executor === targetConfiguration.executor
|
||||||
|
) {
|
||||||
|
result.options = mergeOptions(
|
||||||
|
defaultOptions,
|
||||||
|
targetConfiguration.options ?? {},
|
||||||
|
root,
|
||||||
|
target
|
||||||
|
);
|
||||||
|
result.configurations = mergeConfigurations(
|
||||||
|
defaultConfigurations,
|
||||||
|
targetConfiguration.configurations,
|
||||||
|
root,
|
||||||
|
target
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result as TargetConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeOptions<T extends Object>(
|
||||||
|
defaults: T,
|
||||||
|
options: T,
|
||||||
|
projectRoot: string,
|
||||||
|
key: string
|
||||||
|
): T {
|
||||||
|
return {
|
||||||
|
...resolvePathTokensInOptions(defaults, projectRoot, key),
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeConfigurations<T extends Object>(
|
||||||
|
defaultConfigurations: Record<string, T>,
|
||||||
|
projectDefinedConfigurations: Record<string, T>,
|
||||||
|
projectRoot: string,
|
||||||
|
targetName: string
|
||||||
|
): Record<string, T> {
|
||||||
|
const configurations: Record<string, T> = { ...projectDefinedConfigurations };
|
||||||
|
for (const configuration in defaultConfigurations) {
|
||||||
|
configurations[configuration] = mergeOptions(
|
||||||
|
defaultConfigurations[configuration],
|
||||||
|
configurations[configuration],
|
||||||
|
projectRoot,
|
||||||
|
`${targetName}.${configuration}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return configurations;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolvePathTokensInOptions<T extends Object>(
|
||||||
|
object: T,
|
||||||
|
projectRoot: string,
|
||||||
|
key: string
|
||||||
|
): T {
|
||||||
|
for (let [opt, value] of Object.entries(object ?? {})) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
if (value.startsWith('{workspaceRoot}/')) {
|
||||||
|
value = value.replace(/^\{workspaceRoot\}\//, '');
|
||||||
|
}
|
||||||
|
if (value.includes('{workspaceRoot}')) {
|
||||||
|
throw new Error(
|
||||||
|
`${NX_PREFIX} The {workspaceRoot} token is only valid at the beginning of an option. (${key})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
object[opt] = value.replace('{projectRoot}', projectRoot);
|
||||||
|
} else if (typeof value === 'object' && value) {
|
||||||
|
resolvePathTokensInOptions(value, projectRoot, [key, opt].join('.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readTargetDefaultsForTarget(
|
||||||
|
targetName: string,
|
||||||
|
targetDefaults: TargetDefaults,
|
||||||
|
executor?: string
|
||||||
|
): TargetDefaults[string] {
|
||||||
|
if (executor) {
|
||||||
|
// If an executor is defined in project.json, defaults should be read
|
||||||
|
// from the most specific key that matches that executor.
|
||||||
|
// e.g. If executor === run-commands, and the target is named build:
|
||||||
|
// Use, use nx:run-commands if it is present
|
||||||
|
// If not, use build if it is present.
|
||||||
|
const key = [executor, targetName].find((x) => targetDefaults?.[x]);
|
||||||
|
return key ? targetDefaults?.[key] : null;
|
||||||
|
} else {
|
||||||
|
// If the executor is not defined, the only key we have is the target name.
|
||||||
|
return targetDefaults?.[targetName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// we have to do it this way to preserve the order of properties
|
// we have to do it this way to preserve the order of properties
|
||||||
// not to screw up the formatting
|
// not to screw up the formatting
|
||||||
export function renamePropertyWithStableKeys(
|
export function renamePropertyWithStableKeys(
|
||||||
|
|||||||
@ -11,12 +11,13 @@ import { ProjectGraphBuilder } from '../project-graph-builder';
|
|||||||
import { PackageJson } from '../../utils/package-json';
|
import { PackageJson } from '../../utils/package-json';
|
||||||
import { readJsonFile } from '../../utils/fileutils';
|
import { readJsonFile } from '../../utils/fileutils';
|
||||||
import { NxJsonConfiguration } from '../../config/nx-json';
|
import { NxJsonConfiguration } from '../../config/nx-json';
|
||||||
import {
|
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
|
||||||
ProjectConfiguration,
|
|
||||||
TargetConfiguration,
|
|
||||||
} from '../../config/workspace-json-project-json';
|
|
||||||
import { findMatchingProjects } from '../../utils/find-matching-projects';
|
import { findMatchingProjects } from '../../utils/find-matching-projects';
|
||||||
import { NX_PREFIX } from '../../utils/logger';
|
import { NX_PREFIX } from '../../utils/logger';
|
||||||
|
import {
|
||||||
|
mergeTargetConfigurations,
|
||||||
|
readTargetDefaultsForTarget,
|
||||||
|
} from '../../config/workspaces';
|
||||||
|
|
||||||
export function buildWorkspaceProjectNodes(
|
export function buildWorkspaceProjectNodes(
|
||||||
ctx: ProjectGraphProcessorContext,
|
ctx: ProjectGraphProcessorContext,
|
||||||
@ -55,7 +56,6 @@ export function buildWorkspaceProjectNodes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.targets = normalizeProjectTargets(p.targets, nxJson.targetDefaults, key);
|
|
||||||
p.implicitDependencies = normalizeImplicitDependencies(
|
p.implicitDependencies = normalizeImplicitDependencies(
|
||||||
key,
|
key,
|
||||||
p.implicitDependencies,
|
p.implicitDependencies,
|
||||||
@ -69,6 +69,8 @@ export function buildWorkspaceProjectNodes(
|
|||||||
loadNxPlugins(ctx.workspace.plugins)
|
loadNxPlugins(ctx.workspace.plugins)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
p.targets = normalizeProjectTargets(p, nxJson.targetDefaults, key);
|
||||||
|
|
||||||
// TODO: remove in v16
|
// TODO: remove in v16
|
||||||
const projectType =
|
const projectType =
|
||||||
p.projectType === 'application'
|
p.projectType === 'application'
|
||||||
@ -109,26 +111,27 @@ export function buildWorkspaceProjectNodes(
|
|||||||
* Apply target defaults and normalization
|
* Apply target defaults and normalization
|
||||||
*/
|
*/
|
||||||
function normalizeProjectTargets(
|
function normalizeProjectTargets(
|
||||||
targets: Record<string, TargetConfiguration>,
|
project: ProjectConfiguration,
|
||||||
defaultTargets: NxJsonConfiguration['targetDefaults'],
|
targetDefaults: NxJsonConfiguration['targetDefaults'],
|
||||||
projectName: string
|
projectName: string
|
||||||
) {
|
) {
|
||||||
for (const targetName in defaultTargets) {
|
const targets = project.targets;
|
||||||
const target = targets?.[targetName];
|
|
||||||
if (!target) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (defaultTargets[targetName].inputs && !target.inputs) {
|
|
||||||
target.inputs = defaultTargets[targetName].inputs;
|
|
||||||
}
|
|
||||||
if (defaultTargets[targetName].dependsOn && !target.dependsOn) {
|
|
||||||
target.dependsOn = defaultTargets[targetName].dependsOn;
|
|
||||||
}
|
|
||||||
if (defaultTargets[targetName].outputs && !target.outputs) {
|
|
||||||
target.outputs = defaultTargets[targetName].outputs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const target in targets) {
|
for (const target in targets) {
|
||||||
|
const executor =
|
||||||
|
targets[target].executor ?? targets[target].command
|
||||||
|
? 'nx:run-commands'
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const defaults = readTargetDefaultsForTarget(
|
||||||
|
target,
|
||||||
|
targetDefaults,
|
||||||
|
executor
|
||||||
|
);
|
||||||
|
|
||||||
|
if (defaults) {
|
||||||
|
targets[target] = mergeTargetConfigurations(project, target, defaults);
|
||||||
|
}
|
||||||
|
|
||||||
const config = targets[target];
|
const config = targets[target];
|
||||||
if (config.command) {
|
if (config.command) {
|
||||||
if (config.executor) {
|
if (config.executor) {
|
||||||
@ -138,12 +141,13 @@ function normalizeProjectTargets(
|
|||||||
} else {
|
} else {
|
||||||
targets[target] = {
|
targets[target] = {
|
||||||
...targets[target],
|
...targets[target],
|
||||||
executor: 'nx:run-commands',
|
executor,
|
||||||
options: {
|
options: {
|
||||||
...config.options,
|
...config.options,
|
||||||
command: config.command,
|
command: config.command,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
delete config.command;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user