186 lines
5.5 KiB
TypeScript
186 lines
5.5 KiB
TypeScript
import {
|
|
createProjectGraphAsync,
|
|
formatFiles,
|
|
getProjects,
|
|
ProjectConfiguration,
|
|
ProjectGraphProjectNode,
|
|
readNxJson,
|
|
TargetConfiguration,
|
|
TargetDefaults,
|
|
Tree,
|
|
updateNxJson,
|
|
updateProjectConfiguration,
|
|
} from '@nx/devkit';
|
|
import { JestExecutorOptions } from '../../executors/jest/schema';
|
|
import {
|
|
forEachExecutorOptions,
|
|
forEachExecutorOptionsInGraph,
|
|
} from '@nx/devkit/src/generators/executor-options-utils';
|
|
import { readTargetDefaultsForTarget } from 'nx/src/project-graph/utils/project-configuration-utils';
|
|
|
|
export default async function update(tree: Tree) {
|
|
const nxJson = readNxJson(tree);
|
|
|
|
// Don't override anything if there are already target defaults for jest
|
|
if (nxJson.targetDefaults?.['@nx/jest:jest']) {
|
|
return;
|
|
}
|
|
|
|
nxJson.targetDefaults ??= {};
|
|
|
|
/**
|
|
* A set of targets which does not use any other executors
|
|
*/
|
|
const jestTargets = new Set<string>();
|
|
|
|
const graph = await createProjectGraphAsync();
|
|
|
|
forEachExecutorOptionsInGraph(
|
|
graph,
|
|
'@nx/jest:jest',
|
|
(value, proj, targetName) => {
|
|
jestTargets.add(targetName);
|
|
}
|
|
);
|
|
|
|
// Workspace does not use jest?
|
|
if (jestTargets.size === 0) {
|
|
return;
|
|
}
|
|
// Use the project graph so targets which are inferred are considered
|
|
const projects = graph.nodes;
|
|
const projectMap = getProjects(tree);
|
|
|
|
const jestDefaults: TargetConfiguration<Partial<JestExecutorOptions>> =
|
|
(nxJson.targetDefaults['@nx/jest:jest'] = {});
|
|
|
|
// All jest targets have the same name
|
|
if (jestTargets.size === 1) {
|
|
const targetName = Array.from(jestTargets)[0];
|
|
if (nxJson.targetDefaults[targetName]) {
|
|
Object.assign(jestDefaults, nxJson.targetDefaults[targetName]);
|
|
}
|
|
}
|
|
|
|
jestDefaults.cache ??= true;
|
|
|
|
const inputs = ['default'];
|
|
inputs.push(nxJson.namedInputs?.production ? '^production' : '^default');
|
|
if (tree.exists('jest.preset.js')) {
|
|
inputs.push('{workspaceRoot}/jest.preset.js');
|
|
}
|
|
jestDefaults.inputs ??= inputs;
|
|
|
|
// Remember if there were already defaults so we don't assume the executor default
|
|
const passWithNoTestsPreviouslyInDefaults =
|
|
jestDefaults.options?.passWithNoTests !== undefined;
|
|
const ciCiPreviouslyInDefaults =
|
|
jestDefaults.configurations?.ci?.ci !== undefined;
|
|
const ciCodeCoveragePreviouslyInDefaults =
|
|
jestDefaults.configurations?.ci?.codeCoverage !== undefined;
|
|
|
|
jestDefaults.options ??= {};
|
|
jestDefaults.options.passWithNoTests ??= true;
|
|
jestDefaults.configurations ??= {};
|
|
jestDefaults.configurations.ci ??= {};
|
|
jestDefaults.configurations.ci.ci ??= true;
|
|
jestDefaults.configurations.ci.codeCoverage ??= true;
|
|
|
|
// Cleanup old target defaults
|
|
for (const [targetDefaultKey, targetDefault] of Object.entries(
|
|
nxJson.targetDefaults
|
|
)) {
|
|
if (
|
|
!isTargetDefaultUsed(
|
|
targetDefault,
|
|
nxJson.targetDefaults,
|
|
projects,
|
|
projectMap
|
|
)
|
|
) {
|
|
delete nxJson.targetDefaults[targetDefaultKey];
|
|
}
|
|
}
|
|
|
|
updateNxJson(tree, nxJson);
|
|
|
|
forEachExecutorOptions<JestExecutorOptions>(
|
|
tree,
|
|
'@nx/jest:jest',
|
|
(value, proj, targetName, configuration) => {
|
|
const projConfig = projectMap.get(proj);
|
|
|
|
if (!configuration) {
|
|
// Options
|
|
if (value.passWithNoTests === jestDefaults.options.passWithNoTests) {
|
|
delete projConfig.targets[targetName].options.passWithNoTests;
|
|
} else if (!passWithNoTestsPreviouslyInDefaults) {
|
|
projConfig.targets[targetName].options.passWithNoTests ??= false;
|
|
}
|
|
|
|
if (Object.keys(projConfig.targets[targetName].options).length === 0) {
|
|
delete projConfig.targets[targetName].options;
|
|
}
|
|
} else if (configuration === 'ci') {
|
|
// CI Config
|
|
if (value.ci === jestDefaults.configurations.ci.ci) {
|
|
delete projConfig.targets[targetName].configurations.ci.ci;
|
|
} else if (ciCiPreviouslyInDefaults) {
|
|
projConfig.targets[targetName].configurations.ci.ci ??= false;
|
|
}
|
|
if (
|
|
value.codeCoverage === jestDefaults.configurations.ci.codeCoverage
|
|
) {
|
|
delete projConfig.targets[targetName].configurations.ci.codeCoverage;
|
|
} else if (ciCodeCoveragePreviouslyInDefaults) {
|
|
projConfig.targets[targetName].configurations.ci.codeCoverage ??=
|
|
false;
|
|
}
|
|
|
|
if (
|
|
Object.keys(projConfig.targets[targetName].configurations.ci)
|
|
.length === 0
|
|
) {
|
|
delete projConfig.targets[targetName].configurations.ci;
|
|
}
|
|
if (
|
|
Object.keys(projConfig.targets[targetName].configurations).length ===
|
|
0
|
|
) {
|
|
delete projConfig.targets[targetName].configurations;
|
|
}
|
|
}
|
|
|
|
updateProjectConfiguration(tree, proj, projConfig);
|
|
}
|
|
);
|
|
|
|
await formatFiles(tree);
|
|
}
|
|
|
|
/**
|
|
* Checks every target on every project to see if one of them uses the target default
|
|
*/
|
|
function isTargetDefaultUsed(
|
|
targetDefault: Partial<TargetConfiguration>,
|
|
targetDefaults: TargetDefaults,
|
|
projects: Record<string, ProjectGraphProjectNode>,
|
|
projectMap: Map<string, ProjectConfiguration>
|
|
) {
|
|
for (const p of Object.values(projects)) {
|
|
for (const targetName in p.data?.targets ?? {}) {
|
|
if (
|
|
readTargetDefaultsForTarget(
|
|
targetName,
|
|
targetDefaults,
|
|
// It might seem like we should use the graph here too but we don't want to pass an executor which was processed in the graph
|
|
projectMap.get(p.name).targets?.[targetName]?.executor
|
|
) === targetDefault
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|