nx/packages/jest/src/migrations/update-17-1-0/move-options-to-target-defaults.ts

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