import {
addProjectConfiguration,
ExpandedPluginConfiguration,
joinPathFragments,
PluginConfiguration,
readNxJson,
Tree,
updateNxJson
} from '@nx/devkit';
import { findMatchingConfigFiles } from '@nx/devkit/internal';
import { ExpandedLibraryGeneratorSchema } from '../../generator';
interface InferencePlugin {
plugin: string;
targetName: string;
configFile: string;
}
const inferencePlugins: InferencePlugin[] = [
{ plugin: '@nx/eslint/plugin', targetName: 'lint', configFile: 'eslint.config.mjs' },
{ plugin: '@nx/jest/plugin', targetName: 'test', configFile: 'jest.config.ts' }
];
const getProjectConfiguration = (options: ExpandedLibraryGeneratorSchema) => {
return {
name: options.projectName,
projectType: 'library' as const,
root: options.projectRoot,
sourceRoot: joinPathFragments(options.projectRoot, 'src'),
schematics: {
'@nx/angular:component': {
style: 'scss'
}
},
prefix: options.prefix,
targets: {
build: {
executor: '@nx/angular:package',
configurations: {
production: {}
},
options: {
project: `${options.projectRoot}/ng-package.json`,
tsConfig: `${options.projectRoot}/tsconfig.lib.json`
},
dependsOn: ['^build'],
inputs: ['production', '^production']
},
'test-accessibility': {
command: 'jest --config jest.a11y.config.ts --passWithNoTests',
options: { cwd: options.projectRoot },
configurations: {
ci: { ci: true, coverage: true }
},
cache: true,
inputs: ['default', '^production', '{workspaceRoot}/jest.preset.js']
}
}
};
};
function usesTargetName(registration: PluginConfiguration, targetName: string) {
if (typeof registration === 'string') {
return true;
}
const options = registration.options as { targetName?: string } | undefined;
return (options?.targetName ?? targetName) === targetName;
}
function matchesConfigFile(registration: PluginConfiguration, configFile: string) {
if (typeof registration === 'string') {
return true;
}
return (
findMatchingConfigFiles([configFile], registration.include ?? [], registration.exclude ?? [])
.length > 0
);
}
function canIncludeConfigFile(
registration: PluginConfiguration,
configFile: string,
projectPattern: string
): registration is ExpandedPluginConfiguration & { include: string[] } {
if (typeof registration === 'string' || !registration.include) {
return false;
}
return (
findMatchingConfigFiles([configFile], [projectPattern], registration.exclude ?? []).length > 0
);
}
function ensureInferencePlugin(
tree: Tree,
{ plugin, targetName, configFile }: InferencePlugin,
projectRoot: string
) {
const nxJson = readNxJson(tree) ?? {};
nxJson.plugins ??= [];
const projectPattern = `${projectRoot}/*`;
const projectConfigFile = joinPathFragments(projectRoot, configFile);
const pluginRegistrations = nxJson.plugins.filter(
(entry) => (typeof entry === 'string' ? entry : entry.plugin) === plugin
);
const registrationsWithTargetName = pluginRegistrations.filter((entry) =>
usesTargetName(entry, targetName)
);
if (registrationsWithTargetName.some((entry) => matchesConfigFile(entry, projectConfigFile))) {
return;
}
const scopedRegistration = registrationsWithTargetName.find((entry) =>
canIncludeConfigFile(entry, projectConfigFile, projectPattern)
);
if (scopedRegistration) {
scopedRegistration.include.push(projectPattern);
updateNxJson(tree, nxJson);
return;
}
nxJson.plugins.push(
pluginRegistrations.length > 0
? { plugin, include: [projectPattern], options: { targetName } }
: { plugin, options: { targetName } }
);
updateNxJson(tree, nxJson);
}
export async function addProjectConfig(tree: Tree, options: ExpandedLibraryGeneratorSchema) {
const projectConfiguration = getProjectConfiguration(options);
addProjectConfiguration(tree, options.projectName, projectConfiguration);
for (const inferencePlugin of inferencePlugins) {
ensureInferencePlugin(tree, inferencePlugin, options.projectRoot);
}
}