chore(devkit): add util for migrating to a plugin (#19942)
This commit is contained in:
parent
b7fc7192cf
commit
bdde0ddc98
@ -0,0 +1,447 @@
|
|||||||
|
import { Tree } from 'nx/src/generators/tree';
|
||||||
|
import { CreateNodes } from 'nx/src/utils/nx-plugin';
|
||||||
|
import { createTreeWithEmptyWorkspace } from 'nx/src/generators/testing-utils/create-tree-with-empty-workspace';
|
||||||
|
import {
|
||||||
|
addProjectConfiguration,
|
||||||
|
readProjectConfiguration,
|
||||||
|
} from 'nx/src/generators/utils/project-configuration';
|
||||||
|
|
||||||
|
import { replaceProjectConfigurationsWithPlugin } from './replace-project-configuration-with-plugin';
|
||||||
|
|
||||||
|
describe('replaceProjectConfigurationsWithPlugin', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
let createNodes: CreateNodes;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write('proj/file.txt', '');
|
||||||
|
createNodes = [
|
||||||
|
'proj/file.txt',
|
||||||
|
() => ({
|
||||||
|
projects: {
|
||||||
|
proj: {
|
||||||
|
root: 'proj',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'nx:run-commands',
|
||||||
|
dependsOn: ['^build-base'],
|
||||||
|
inputs: ['default', '^default'],
|
||||||
|
outputs: ['{options.output}', '{projectRoot}/outputs'],
|
||||||
|
options: {
|
||||||
|
configFile: 'file.txt',
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
production: {
|
||||||
|
configFile: 'file.prod.txt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not update the target when it uses a different executor', async () => {
|
||||||
|
const buildTarget = {
|
||||||
|
executor: 'nx:run-script',
|
||||||
|
inputs: ['default', '^default'],
|
||||||
|
outputs: ['{options.output}', '{projectRoot}/outputs'],
|
||||||
|
options: {
|
||||||
|
configFile: 'file.txt',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
addProjectConfiguration(tree, 'proj', {
|
||||||
|
root: 'proj',
|
||||||
|
targets: {
|
||||||
|
build: buildTarget,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
replaceProjectConfigurationsWithPlugin(
|
||||||
|
tree,
|
||||||
|
new Map([['proj', 'proj']]),
|
||||||
|
'plugin-path',
|
||||||
|
createNodes,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(readProjectConfiguration(tree, 'proj').targets.build).toEqual(
|
||||||
|
buildTarget
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('options', () => {
|
||||||
|
it('should be removed when there are no other options', async () => {
|
||||||
|
addProjectConfiguration(tree, 'proj', {
|
||||||
|
root: 'proj',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'nx:run-commands',
|
||||||
|
inputs: ['default', '^default'],
|
||||||
|
outputs: ['{options.output}', '{projectRoot}/outputs'],
|
||||||
|
options: {
|
||||||
|
configFile: 'file.txt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
replaceProjectConfigurationsWithPlugin(
|
||||||
|
tree,
|
||||||
|
new Map([['proj', 'proj']]),
|
||||||
|
'plugin-path',
|
||||||
|
createNodes,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
readProjectConfiguration(tree, 'proj').targets.build
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be removed when there are other options', async () => {
|
||||||
|
addProjectConfiguration(tree, 'proj', {
|
||||||
|
root: 'proj',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'nx:run-commands',
|
||||||
|
inputs: ['default', '^default'],
|
||||||
|
outputs: ['{options.output}', '{projectRoot}/outputs'],
|
||||||
|
options: {
|
||||||
|
configFile: 'file.txt',
|
||||||
|
watch: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
replaceProjectConfigurationsWithPlugin(
|
||||||
|
tree,
|
||||||
|
new Map([['proj', 'proj']]),
|
||||||
|
'plugin-path',
|
||||||
|
createNodes,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(readProjectConfiguration(tree, 'proj').targets.build).toEqual({
|
||||||
|
options: {
|
||||||
|
watch: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('inputs', () => {
|
||||||
|
it('should not be removed if there are additional inputs', () => {
|
||||||
|
addProjectConfiguration(tree, 'proj', {
|
||||||
|
root: 'proj',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'nx:run-commands',
|
||||||
|
inputs: ['default', '^default', '{workspaceRoot}/file.txt'],
|
||||||
|
outputs: ['{options.output}', '{projectRoot}/outputs'],
|
||||||
|
options: {
|
||||||
|
configFile: 'file.txt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
replaceProjectConfigurationsWithPlugin(
|
||||||
|
tree,
|
||||||
|
new Map([['proj', 'proj']]),
|
||||||
|
'plugin-path',
|
||||||
|
createNodes,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(readProjectConfiguration(tree, 'proj').targets.build).toEqual({
|
||||||
|
inputs: ['default', '^default', '{workspaceRoot}/file.txt'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be removed if there are additional inputs which are objects', () => {
|
||||||
|
addProjectConfiguration(tree, 'proj', {
|
||||||
|
root: 'proj',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'nx:run-commands',
|
||||||
|
inputs: [
|
||||||
|
'default',
|
||||||
|
'^default',
|
||||||
|
{
|
||||||
|
env: 'HOME',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: ['{options.output}', '{projectRoot}/outputs'],
|
||||||
|
options: {
|
||||||
|
configFile: 'file.txt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
replaceProjectConfigurationsWithPlugin(
|
||||||
|
tree,
|
||||||
|
new Map([['proj', 'proj']]),
|
||||||
|
'plugin-path',
|
||||||
|
createNodes,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(readProjectConfiguration(tree, 'proj').targets.build).toEqual({
|
||||||
|
inputs: [
|
||||||
|
'default',
|
||||||
|
'^default',
|
||||||
|
{
|
||||||
|
env: 'HOME',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be removed if there are less inputs', () => {
|
||||||
|
addProjectConfiguration(tree, 'proj', {
|
||||||
|
root: 'proj',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'nx:run-commands',
|
||||||
|
inputs: ['default'],
|
||||||
|
outputs: ['{options.output}', '{projectRoot}/outputs'],
|
||||||
|
options: {
|
||||||
|
configFile: 'file.txt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
replaceProjectConfigurationsWithPlugin(
|
||||||
|
tree,
|
||||||
|
new Map([['proj', 'proj']]),
|
||||||
|
'plugin-path',
|
||||||
|
createNodes,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(readProjectConfiguration(tree, 'proj').targets.build).toEqual({
|
||||||
|
inputs: ['default'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('outputs', () => {
|
||||||
|
it('should not be removed if there are additional outputs', () => {
|
||||||
|
addProjectConfiguration(tree, 'proj', {
|
||||||
|
root: 'proj',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'nx:run-commands',
|
||||||
|
inputs: ['default', '^default'],
|
||||||
|
outputs: [
|
||||||
|
'{options.output}',
|
||||||
|
'{projectRoot}/outputs',
|
||||||
|
'{projectRoot}/more-outputs',
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
configFile: 'file.txt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
replaceProjectConfigurationsWithPlugin(
|
||||||
|
tree,
|
||||||
|
new Map([['proj', 'proj']]),
|
||||||
|
'plugin-path',
|
||||||
|
createNodes,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(readProjectConfiguration(tree, 'proj').targets.build).toEqual({
|
||||||
|
outputs: [
|
||||||
|
'{options.output}',
|
||||||
|
'{projectRoot}/outputs',
|
||||||
|
'{projectRoot}/more-outputs',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be removed if there are less outputs', () => {
|
||||||
|
addProjectConfiguration(tree, 'proj', {
|
||||||
|
root: 'proj',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'nx:run-commands',
|
||||||
|
outputs: ['{options.output}'],
|
||||||
|
options: {
|
||||||
|
configFile: 'file.txt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
replaceProjectConfigurationsWithPlugin(
|
||||||
|
tree,
|
||||||
|
new Map([['proj', 'proj']]),
|
||||||
|
'plugin-path',
|
||||||
|
createNodes,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(readProjectConfiguration(tree, 'proj').targets.build).toEqual({
|
||||||
|
outputs: ['{options.output}'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('dependsOn', () => {
|
||||||
|
it('should be removed when it is the same', () => {
|
||||||
|
addProjectConfiguration(tree, 'proj', {
|
||||||
|
root: 'proj',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'nx:run-commands',
|
||||||
|
dependsOn: ['^build-base'],
|
||||||
|
options: {
|
||||||
|
configFile: 'file.txt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
replaceProjectConfigurationsWithPlugin(
|
||||||
|
tree,
|
||||||
|
new Map([['proj', 'proj']]),
|
||||||
|
'plugin-path',
|
||||||
|
createNodes,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
readProjectConfiguration(tree, 'proj').targets.build
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be removed when there are more dependent tasks', () => {
|
||||||
|
addProjectConfiguration(tree, 'proj', {
|
||||||
|
root: 'proj',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'nx:run-commands',
|
||||||
|
dependsOn: ['^build-base', 'prebuild'],
|
||||||
|
options: {
|
||||||
|
configFile: 'file.txt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
replaceProjectConfigurationsWithPlugin(
|
||||||
|
tree,
|
||||||
|
new Map([['proj', 'proj']]),
|
||||||
|
'plugin-path',
|
||||||
|
createNodes,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(readProjectConfiguration(tree, 'proj').targets.build).toEqual({
|
||||||
|
dependsOn: ['^build-base', 'prebuild'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be removed when there are less dependent tasks', () => {
|
||||||
|
addProjectConfiguration(tree, 'proj', {
|
||||||
|
root: 'proj',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'nx:run-commands',
|
||||||
|
dependsOn: [],
|
||||||
|
options: {
|
||||||
|
configFile: 'file.txt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
replaceProjectConfigurationsWithPlugin(
|
||||||
|
tree,
|
||||||
|
new Map([['proj', 'proj']]),
|
||||||
|
'plugin-path',
|
||||||
|
createNodes,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(readProjectConfiguration(tree, 'proj').targets.build).toEqual({
|
||||||
|
dependsOn: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('defaultConfiguration', () => {
|
||||||
|
it('should not be removed when the defaultConfiguration is different', () => {
|
||||||
|
addProjectConfiguration(tree, 'proj', {
|
||||||
|
root: 'proj',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'nx:run-commands',
|
||||||
|
options: {
|
||||||
|
configFile: 'file.txt',
|
||||||
|
},
|
||||||
|
defaultConfiguration: 'other',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
replaceProjectConfigurationsWithPlugin(
|
||||||
|
tree,
|
||||||
|
new Map([['proj', 'proj']]),
|
||||||
|
'plugin-path',
|
||||||
|
createNodes,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(readProjectConfiguration(tree, 'proj').targets.build).toEqual({
|
||||||
|
defaultConfiguration: 'other',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('configurations', () => {
|
||||||
|
it('should not be removed when an additional configuration is defined', () => {
|
||||||
|
addProjectConfiguration(tree, 'proj', {
|
||||||
|
root: 'proj',
|
||||||
|
targets: {
|
||||||
|
build: {
|
||||||
|
executor: 'nx:run-commands',
|
||||||
|
options: {
|
||||||
|
configFile: 'file.txt',
|
||||||
|
},
|
||||||
|
configurations: {
|
||||||
|
other: {
|
||||||
|
configFile: 'other-file.txt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
replaceProjectConfigurationsWithPlugin(
|
||||||
|
tree,
|
||||||
|
new Map([['proj', 'proj']]),
|
||||||
|
'plugin-path',
|
||||||
|
createNodes,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(readProjectConfiguration(tree, 'proj').targets.build).toEqual({
|
||||||
|
configurations: {
|
||||||
|
other: {
|
||||||
|
configFile: 'other-file.txt',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,206 @@
|
|||||||
|
import type {
|
||||||
|
ProjectConfiguration,
|
||||||
|
TargetConfiguration,
|
||||||
|
} from 'nx/src/config/workspace-json-project-json';
|
||||||
|
import type { Tree } from 'nx/src/generators/tree';
|
||||||
|
import type { CreateNodes } from 'nx/src/utils/nx-plugin';
|
||||||
|
import { requireNx } from '../../nx';
|
||||||
|
const {
|
||||||
|
readNxJson,
|
||||||
|
updateNxJson,
|
||||||
|
glob,
|
||||||
|
hashObject,
|
||||||
|
findProjectForPath,
|
||||||
|
readProjectConfiguration,
|
||||||
|
updateProjectConfiguration,
|
||||||
|
} = requireNx();
|
||||||
|
|
||||||
|
export function replaceProjectConfigurationsWithPlugin<T = unknown>(
|
||||||
|
tree: Tree,
|
||||||
|
rootMappings: Map<string, string>,
|
||||||
|
pluginPath: string,
|
||||||
|
createNodes: CreateNodes<T>,
|
||||||
|
pluginOptions: T
|
||||||
|
): void {
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
const hasPlugin = nxJson.plugins?.some((p) =>
|
||||||
|
typeof p === 'string' ? p === pluginPath : p.plugin === pluginPath
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasPlugin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nxJson.plugins ??= [];
|
||||||
|
nxJson.plugins.push({
|
||||||
|
plugin: pluginPath,
|
||||||
|
options: pluginOptions,
|
||||||
|
});
|
||||||
|
updateNxJson(tree, nxJson);
|
||||||
|
|
||||||
|
const [pluginGlob, createNodesFunction] = createNodes;
|
||||||
|
const configFiles = glob(tree, [pluginGlob]);
|
||||||
|
|
||||||
|
for (const configFile of configFiles) {
|
||||||
|
try {
|
||||||
|
const projectName = findProjectForPath(configFile, rootMappings);
|
||||||
|
const projectConfig = readProjectConfiguration(tree, projectName);
|
||||||
|
const nodes = createNodesFunction(configFile, pluginOptions, {
|
||||||
|
workspaceRoot: tree.root,
|
||||||
|
nxJsonConfiguration: readNxJson(tree),
|
||||||
|
});
|
||||||
|
const node = nodes.projects[Object.keys(nodes.projects)[0]];
|
||||||
|
|
||||||
|
for (const [targetName, targetConfig] of Object.entries(node.targets)) {
|
||||||
|
const targetFromProjectConfig = projectConfig.targets[targetName];
|
||||||
|
|
||||||
|
if (targetFromProjectConfig.executor !== targetConfig.executor) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetFromCreateNodes = node.targets[targetName];
|
||||||
|
|
||||||
|
removeConfigurationDefinedByPlugin(
|
||||||
|
targetName,
|
||||||
|
targetFromProjectConfig,
|
||||||
|
targetFromCreateNodes,
|
||||||
|
projectConfig
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProjectConfiguration(tree, projectName, projectConfig);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeConfigurationDefinedByPlugin<T>(
|
||||||
|
targetName: string,
|
||||||
|
targetFromProjectConfig: TargetConfiguration<T>,
|
||||||
|
targetFromCreateNodes: TargetConfiguration<T>,
|
||||||
|
projectConfig: ProjectConfiguration
|
||||||
|
) {
|
||||||
|
// Executor
|
||||||
|
delete targetFromProjectConfig.executor;
|
||||||
|
|
||||||
|
// Default Configuration
|
||||||
|
if (
|
||||||
|
targetFromProjectConfig.defaultConfiguration ===
|
||||||
|
targetFromCreateNodes.defaultConfiguration
|
||||||
|
) {
|
||||||
|
delete targetFromProjectConfig.defaultConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache
|
||||||
|
if (targetFromProjectConfig.cache === targetFromCreateNodes.cache) {
|
||||||
|
delete targetFromProjectConfig.cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Depends On
|
||||||
|
if (
|
||||||
|
targetFromProjectConfig.dependsOn &&
|
||||||
|
shouldRemoveArrayProperty(
|
||||||
|
targetFromProjectConfig.dependsOn,
|
||||||
|
targetFromCreateNodes.dependsOn
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
delete targetFromProjectConfig.dependsOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outputs
|
||||||
|
if (
|
||||||
|
targetFromProjectConfig.outputs &&
|
||||||
|
shouldRemoveArrayProperty(
|
||||||
|
targetFromProjectConfig.outputs,
|
||||||
|
targetFromCreateNodes.outputs
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
delete targetFromProjectConfig.outputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inputs
|
||||||
|
if (
|
||||||
|
targetFromProjectConfig.inputs &&
|
||||||
|
shouldRemoveArrayProperty(
|
||||||
|
targetFromProjectConfig.inputs,
|
||||||
|
targetFromCreateNodes.inputs
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
delete targetFromProjectConfig.inputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options
|
||||||
|
for (const [optionName, optionValue] of Object.entries(
|
||||||
|
targetFromProjectConfig.options ?? {}
|
||||||
|
)) {
|
||||||
|
if (targetFromCreateNodes.options[optionName] === optionValue) {
|
||||||
|
delete targetFromProjectConfig.options[optionName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(targetFromProjectConfig.options).length === 0) {
|
||||||
|
delete targetFromProjectConfig.options;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configurations
|
||||||
|
for (const [configName, configOptions] of Object.entries(
|
||||||
|
targetFromProjectConfig.configurations ?? {}
|
||||||
|
)) {
|
||||||
|
for (const [optionName, optionValue] of Object.entries(configOptions)) {
|
||||||
|
if (
|
||||||
|
targetFromCreateNodes.configurations?.[configName]?.[optionName] ===
|
||||||
|
optionValue
|
||||||
|
) {
|
||||||
|
delete targetFromProjectConfig.configurations[configName][optionName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(configOptions).length === 0) {
|
||||||
|
delete targetFromProjectConfig.configurations[configName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(targetFromProjectConfig.configurations ?? {}).length === 0) {
|
||||||
|
delete targetFromProjectConfig.configurations;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(targetFromProjectConfig).length === 0) {
|
||||||
|
delete projectConfig.targets[targetName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldRemoveArrayProperty(
|
||||||
|
arrayValuesFromProjectConfiguration: (object | string)[],
|
||||||
|
arrayValuesFromCreateNodes: (object | string)[]
|
||||||
|
) {
|
||||||
|
const setOfArrayValuesFromProjectConfiguration = new Set(
|
||||||
|
arrayValuesFromProjectConfiguration
|
||||||
|
);
|
||||||
|
loopThroughArrayValuesFromCreateNodes: for (const arrayValueFromCreateNodes of arrayValuesFromCreateNodes) {
|
||||||
|
if (typeof arrayValueFromCreateNodes === 'string') {
|
||||||
|
if (
|
||||||
|
!setOfArrayValuesFromProjectConfiguration.has(arrayValueFromCreateNodes)
|
||||||
|
) {
|
||||||
|
// If the inputs from the project configuration is missing an input from createNodes it was removed
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
setOfArrayValuesFromProjectConfiguration.delete(
|
||||||
|
arrayValueFromCreateNodes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const arrayValue of setOfArrayValuesFromProjectConfiguration.values()) {
|
||||||
|
if (
|
||||||
|
typeof arrayValue !== 'string' &&
|
||||||
|
hashObject(arrayValue) === hashObject(arrayValueFromCreateNodes)
|
||||||
|
) {
|
||||||
|
setOfArrayValuesFromProjectConfiguration.delete(arrayValue);
|
||||||
|
// Continue the outer loop, breaking out of this loop
|
||||||
|
continue loopThroughArrayValuesFromCreateNodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If an input was not matched, that means the input was removed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If there are still inputs in the project configuration, they have added additional inputs
|
||||||
|
return setOfArrayValuesFromProjectConfiguration.size === 0;
|
||||||
|
}
|
||||||
@ -14,6 +14,7 @@ export { sortObjectByKeys } from './utils/object-sort';
|
|||||||
export { stripIndent } from './utils/logger';
|
export { stripIndent } from './utils/logger';
|
||||||
export { readModulePackageJson } from './utils/package-json';
|
export { readModulePackageJson } from './utils/package-json';
|
||||||
export { splitByColons } from './utils/split-target';
|
export { splitByColons } from './utils/split-target';
|
||||||
|
export { hashObject } from './hasher/file-hasher';
|
||||||
export {
|
export {
|
||||||
createProjectRootMappingsFromProjectConfigurations,
|
createProjectRootMappingsFromProjectConfigurations,
|
||||||
findProjectForPath,
|
findProjectForPath,
|
||||||
|
|||||||
@ -6,7 +6,7 @@ exports[`create-nodes-plugin/generator generator should run successfully 1`] = `
|
|||||||
CreateNodesContext,
|
CreateNodesContext,
|
||||||
TargetConfiguration,
|
TargetConfiguration,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { basename, dirname, extname, join, resolve } from "path";
|
import { basename, dirname, extname, join, resolve } from 'path';
|
||||||
import { registerTsProject } from '@nx/js/src/internal';
|
import { registerTsProject } from '@nx/js/src/internal';
|
||||||
|
|
||||||
import { getRootTsConfigPath } from '@nx/js';
|
import { getRootTsConfigPath } from '@nx/js';
|
||||||
@ -45,7 +45,7 @@ export const createNodes: CreateNodes<EslintPluginOptions> = [
|
|||||||
configFilePath,
|
configFilePath,
|
||||||
projectRoot,
|
projectRoot,
|
||||||
options,
|
options,
|
||||||
context,
|
context
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -57,12 +57,9 @@ function buildEslintTargets(
|
|||||||
configFilePath: string,
|
configFilePath: string,
|
||||||
projectRoot: string,
|
projectRoot: string,
|
||||||
options: EslintPluginOptions,
|
options: EslintPluginOptions,
|
||||||
context: CreateNodesContext,
|
context: CreateNodesContext
|
||||||
) {
|
) {
|
||||||
const eslintConfig = getEslintConfig(
|
const eslintConfig = getEslintConfig(configFilePath, context);
|
||||||
configFilePath,
|
|
||||||
context
|
|
||||||
);
|
|
||||||
|
|
||||||
const targetDefaults = readTargetDefaultsForTarget(
|
const targetDefaults = readTargetDefaultsForTarget(
|
||||||
options.targetName,
|
options.targetName,
|
||||||
@ -72,10 +69,7 @@ function buildEslintTargets(
|
|||||||
|
|
||||||
const namedInputs = getNamedInputs(projectRoot, context);
|
const namedInputs = getNamedInputs(projectRoot, context);
|
||||||
|
|
||||||
const targets: Record<
|
const targets: Record<string, TargetConfiguration<ExecutorOptions>> = {};
|
||||||
string,
|
|
||||||
TargetConfiguration<ExecutorOptions>
|
|
||||||
> = {};
|
|
||||||
|
|
||||||
const baseTargetConfig: TargetConfiguration<ExecutorOptions> = {
|
const baseTargetConfig: TargetConfiguration<ExecutorOptions> = {
|
||||||
executor: 'executorName',
|
executor: 'executorName',
|
||||||
@ -92,9 +86,7 @@ function buildEslintTargets(
|
|||||||
targetDefaults?.inputs ?? 'production' in namedInputs
|
targetDefaults?.inputs ?? 'production' in namedInputs
|
||||||
? ['default', '^production']
|
? ['default', '^production']
|
||||||
: ['default', '^default'],
|
: ['default', '^default'],
|
||||||
outputs:
|
outputs: targetDefaults?.outputs ?? getOutputs(projectRoot),
|
||||||
targetDefaults?.outputs ??
|
|
||||||
getOutputs(projectRoot),
|
|
||||||
options: {
|
options: {
|
||||||
...baseTargetConfig.options,
|
...baseTargetConfig.options,
|
||||||
},
|
},
|
||||||
@ -129,9 +121,7 @@ function getEslintConfig(
|
|||||||
return module.default ?? module;
|
return module.default ?? module;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOutputs(
|
function getOutputs(projectRoot: string): string[] {
|
||||||
projectRoot: string,
|
|
||||||
): string[] {
|
|
||||||
function getOutput(path: string): string {
|
function getOutput(path: string): string {
|
||||||
if (path.startsWith('..')) {
|
if (path.startsWith('..')) {
|
||||||
return join('{workspaceRoot}', join(projectRoot, path));
|
return join('{workspaceRoot}', join(projectRoot, path));
|
||||||
@ -179,7 +169,7 @@ describe('@nx/eslint/plugin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should create nodes', () => {
|
it('should create nodes', () => {
|
||||||
mockEslintConfig({})
|
mockEslintConfig({});
|
||||||
const nodes = createNodesFunction(
|
const nodes = createNodesFunction(
|
||||||
'TODO',
|
'TODO',
|
||||||
{
|
{
|
||||||
@ -192,10 +182,7 @@ describe('@nx/eslint/plugin', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function mockEslintConfig(config: any) {
|
||||||
function mockEslintConfig(
|
|
||||||
config: any
|
|
||||||
) {
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'TODO',
|
'TODO',
|
||||||
() => ({
|
() => ({
|
||||||
@ -208,3 +195,30 @@ function mockEslintConfig(
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`create-nodes-plugin/generator generator should run successfully 3`] = `
|
||||||
|
"import { formatFiles, getProjects, Tree } from '@nx/devkit';
|
||||||
|
import { createNodes } from '../../plugins/plugin';
|
||||||
|
|
||||||
|
import { createProjectRootMappingsFromProjectConfigurations } from 'nx/src/project-graph/utils/find-project-for-path';
|
||||||
|
import { replaceProjectConfigurationsWithPlugin } from '@nx/devkit/src/utils/replace-project-configuration-with-plugin';
|
||||||
|
|
||||||
|
export default async function update(tree: Tree) {
|
||||||
|
const proj = Object.fromEntries(getProjects(tree).entries());
|
||||||
|
|
||||||
|
const rootMappings = createProjectRootMappingsFromProjectConfigurations(proj);
|
||||||
|
|
||||||
|
replaceProjectConfigurationsWithPlugin(
|
||||||
|
tree,
|
||||||
|
rootMappings,
|
||||||
|
'@nx/eslint/plugin',
|
||||||
|
createNodes,
|
||||||
|
{
|
||||||
|
targetName: 'TODO',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { formatFiles, getProjects, Tree } from '@nx/devkit';
|
||||||
|
import { createNodes } from '../../plugins/plugin';
|
||||||
|
|
||||||
|
import { createProjectRootMappingsFromProjectConfigurations } from 'nx/src/project-graph/utils/find-project-for-path';
|
||||||
|
import { replaceProjectConfigurationsWithPlugin } from '@nx/devkit/src/utils/replace-project-configuration-with-plugin';
|
||||||
|
|
||||||
|
export default async function update(tree: Tree) {
|
||||||
|
const proj = Object.fromEntries(getProjects(tree).entries());
|
||||||
|
|
||||||
|
const rootMappings = createProjectRootMappingsFromProjectConfigurations(proj);
|
||||||
|
|
||||||
|
replaceProjectConfigurationsWithPlugin(
|
||||||
|
tree,
|
||||||
|
rootMappings,
|
||||||
|
'@nx/<%= dirName %>/plugin',
|
||||||
|
createNodes,
|
||||||
|
{
|
||||||
|
targetName: 'TODO',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
@ -1,25 +1,43 @@
|
|||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import { Tree } from '@nx/devkit';
|
import { addProjectConfiguration, Tree, writeJson } from '@nx/devkit';
|
||||||
|
|
||||||
import { generatorGenerator, GeneratorGeneratorSchema } from './generator';
|
import { generatorGenerator } from './generator';
|
||||||
|
import { setCwd } from '@nx/devkit/internal-testing-utils';
|
||||||
|
|
||||||
describe('create-nodes-plugin/generator generator', () => {
|
describe('create-nodes-plugin/generator generator', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
const options: GeneratorGeneratorSchema = {};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tree = createTreeWithEmptyWorkspace();
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
|
||||||
|
addProjectConfiguration(tree, 'eslint', {
|
||||||
|
root: 'packages/eslint',
|
||||||
|
targets: {
|
||||||
|
build: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
writeJson(tree, 'packages/eslint/package.json', {});
|
||||||
|
|
||||||
jest.spyOn(process, 'cwd').mockReturnValue('/virtual/packages/eslint');
|
jest.spyOn(process, 'cwd').mockReturnValue('/virtual/packages/eslint');
|
||||||
|
|
||||||
|
setCwd('packages/eslint');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run successfully', async () => {
|
it('should run successfully', async () => {
|
||||||
await generatorGenerator(tree, options);
|
await generatorGenerator(tree);
|
||||||
expect(
|
expect(
|
||||||
tree.read('packages/eslint/src/plugins/plugin.ts').toString()
|
tree.read('packages/eslint/src/plugins/plugin.ts').toString()
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
expect(
|
expect(
|
||||||
tree.read('packages/eslint/src/plugins/plugin.spec.ts').toString()
|
tree.read('packages/eslint/src/plugins/plugin.spec.ts').toString()
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree
|
||||||
|
.read(
|
||||||
|
'packages/eslint/src/migrations/update-17-2-0/add-eslint-plugin.ts'
|
||||||
|
)
|
||||||
|
.toString()
|
||||||
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,20 +1,27 @@
|
|||||||
import { generateFiles, names, Tree } from '@nx/devkit';
|
import { formatFiles, generateFiles, names, Tree } from '@nx/devkit';
|
||||||
import { basename, join, relative } from 'path';
|
import { basename, join, relative } from 'path';
|
||||||
|
import migrationGenerator from '@nx/plugin/src/generators/migration/migration';
|
||||||
|
|
||||||
export interface GeneratorGeneratorSchema {}
|
export async function generatorGenerator(tree: Tree) {
|
||||||
|
|
||||||
export async function generatorGenerator(
|
|
||||||
tree: Tree,
|
|
||||||
options: GeneratorGeneratorSchema
|
|
||||||
) {
|
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
const { className, propertyName } = names(basename(cwd));
|
const { className, propertyName } = names(basename(cwd));
|
||||||
|
|
||||||
|
await migrationGenerator(tree, {
|
||||||
|
name: `add-${basename(cwd)}-plugin`,
|
||||||
|
packageVersion: '17.2.0-beta.0',
|
||||||
|
description: `Add @nx/${basename(cwd)}/plugin`,
|
||||||
|
nameAndDirectoryFormat: 'as-provided',
|
||||||
|
directory: `src/migrations/update-17-2-0`,
|
||||||
|
skipFormat: true,
|
||||||
|
});
|
||||||
|
|
||||||
generateFiles(tree, join(__dirname, 'files'), relative(tree.root, cwd), {
|
generateFiles(tree, join(__dirname, 'files'), relative(tree.root, cwd), {
|
||||||
dirName: basename(cwd),
|
dirName: basename(cwd),
|
||||||
className,
|
className,
|
||||||
propertyName,
|
propertyName,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await formatFiles(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default generatorGenerator;
|
export default generatorGenerator;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user