fix(misc): register plugins correctly in migration generators (#26670)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->
<!-- Fixes NXP-816 -->

Fixes #
This commit is contained in:
Leosvel Pérez Espinosa 2024-06-25 14:33:24 +02:00 committed by GitHub
parent 8872ca5c8f
commit c936f864b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 815 additions and 557 deletions

View File

@ -495,33 +495,46 @@ describe('Cypress - Convert Executors To Plugin', () => {
// nx.json modifications // nx.json modifications
const nxJsonPlugins = readNxJson(tree).plugins; const nxJsonPlugins = readNxJson(tree).plugins;
const addedTestCypressPlugin = nxJsonPlugins.find((plugin) => { const addedCypressPlugins = nxJsonPlugins.filter(
if ( (plugin) =>
typeof plugin !== 'string' && typeof plugin !== 'string' && plugin.plugin === '@nx/cypress/plugin'
plugin.plugin === '@nx/cypress/plugin' && );
plugin.include?.length === 2 expect(addedCypressPlugins).toMatchInlineSnapshot(`
) { [
return true; {
} "options": {
}); "ciTargetName": "e2e-ci",
expect(addedTestCypressPlugin).toBeTruthy(); "targetName": "e2e",
expect( },
(addedTestCypressPlugin as ExpandedPluginConfiguration).include "plugin": "@nx/cypress/plugin",
).toEqual(['myapp-e2e/**/*', 'second/**/*']); },
{
const addedIntegrationCypressPlugin = nxJsonPlugins.find((plugin) => { "include": [
if ( "myapp-e2e/**/*",
typeof plugin !== 'string' && "second/**/*",
plugin.plugin === '@nx/cypress/plugin' && ],
plugin.include?.length === 1 "options": {
) { "ciTargetName": "e2e-ci",
return true; "componentTestingTargetName": "component-test",
} "openTargetName": "open-cypress",
}); "targetName": "test",
expect(addedIntegrationCypressPlugin).toBeTruthy(); },
expect( "plugin": "@nx/cypress/plugin",
(addedIntegrationCypressPlugin as ExpandedPluginConfiguration).include },
).toEqual(['third/**/*']); {
"include": [
"third/**/*",
],
"options": {
"ciTargetName": "e2e-ci",
"componentTestingTargetName": "component-test",
"openTargetName": "open-cypress",
"targetName": "integration",
},
"plugin": "@nx/cypress/plugin",
},
]
`);
}); });
it('should keep Cypress options in project.json', async () => { it('should keep Cypress options in project.json', async () => {

View File

@ -4,16 +4,16 @@ import {
type TargetConfiguration, type TargetConfiguration,
type Tree, type Tree,
} from '@nx/devkit'; } from '@nx/devkit';
import { migrateExecutorToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator'; import { migrateProjectExecutorsToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import { createNodesV2 } from '../../plugins/plugin';
import { targetOptionsToCliMap } from './lib/target-options-map';
import { upsertBaseUrl } from './lib/upsert-baseUrl';
import { addDevServerTargetToConfig } from './lib/add-dev-server-target-to-config';
import { addExcludeSpecPattern } from './lib/add-exclude-spec-pattern';
import { import {
processTargetOutputs, processTargetOutputs,
toProjectRelativePath, toProjectRelativePath,
} from '@nx/devkit/src/generators/plugin-migrations/plugin-migration-utils'; } from '@nx/devkit/src/generators/plugin-migrations/plugin-migration-utils';
import { createNodesV2, type CypressPluginOptions } from '../../plugins/plugin';
import { addDevServerTargetToConfig } from './lib/add-dev-server-target-to-config';
import { addExcludeSpecPattern } from './lib/add-exclude-spec-pattern';
import { targetOptionsToCliMap } from './lib/target-options-map';
import { upsertBaseUrl } from './lib/upsert-baseUrl';
interface Schema { interface Schema {
project?: string; project?: string;
@ -23,38 +23,29 @@ interface Schema {
export async function convertToInferred(tree: Tree, options: Schema) { export async function convertToInferred(tree: Tree, options: Schema) {
const projectGraph = await createProjectGraphAsync(); const projectGraph = await createProjectGraphAsync();
const migratedProjectsModern = await migrateExecutorToPlugin(
tree,
projectGraph,
'@nx/cypress:cypress',
'@nx/cypress/plugin',
(targetName) => ({
targetName,
ciTargetName: 'e2e-ci',
}),
postTargetTransformer,
createNodesV2,
options.project
);
const migratedProjectsLegacy = await migrateExecutorToPlugin(
tree,
projectGraph,
'@nrwl/cypress:cypress',
'@nx/cypress/plugin',
(targetName) => ({
targetName,
ciTargetName: 'e2e-ci',
}),
postTargetTransformer,
createNodesV2,
options.project
);
const migratedProjects = const migratedProjects =
migratedProjectsModern.size + migratedProjectsLegacy.size; await migrateProjectExecutorsToPlugin<CypressPluginOptions>(
tree,
projectGraph,
'@nx/cypress/plugin',
createNodesV2,
{
targetName: 'cypress',
ciTargetName: 'e2e-ci',
componentTestingTargetName: 'component-test',
openTargetName: 'open-cypress',
},
[
{
executors: ['@nx/cypress:cypress', '@nrwl/cypress:cypress'],
postTargetTransformer,
targetPluginOptionMapper: (targetName) => ({ targetName }),
},
],
options.project
);
if (migratedProjects === 0) { if (migratedProjects.size === 0) {
throw new Error('Could not find any targets to migrate.'); throw new Error('Could not find any targets to migrate.');
} }

View File

@ -1,36 +1,32 @@
import type { RunCommandsOptions } from 'nx/src/executors/run-commands/run-commands.impl';
import { minimatch } from 'minimatch'; import { minimatch } from 'minimatch';
import { deepStrictEqual } from 'node:assert'; import { deepStrictEqual } from 'node:assert';
import { forEachExecutorOptions } from '../executor-options-utils';
import { deleteMatchingProperties } from './plugin-migration-utils';
import {
readNxJson,
updateNxJson,
updateProjectConfiguration,
readProjectConfiguration,
ProjectGraph,
ExpandedPluginConfiguration,
NxJsonConfiguration,
TargetConfiguration,
Tree,
CreateNodes,
CreateNodesV2,
} from 'nx/src/devkit-exports';
import {
mergeTargetConfigurations,
retrieveProjectConfigurations,
LoadedNxPlugin,
ProjectConfigurationsError,
} from 'nx/src/devkit-internals';
import type { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils';
import type { import type {
InputDefinition, InputDefinition,
ProjectConfiguration, ProjectConfiguration,
} from 'nx/src/config/workspace-json-project-json'; } from 'nx/src/config/workspace-json-project-json';
import {
readNxJson,
readProjectConfiguration,
updateNxJson,
updateProjectConfiguration,
type CreateNodes,
type CreateNodesV2,
type ExpandedPluginConfiguration,
type NxJsonConfiguration,
type ProjectGraph,
type TargetConfiguration,
type Tree,
} from 'nx/src/devkit-exports';
import {
LoadedNxPlugin,
ProjectConfigurationsError,
mergeTargetConfigurations,
retrieveProjectConfigurations,
} from 'nx/src/devkit-internals';
import type { RunCommandsOptions } from 'nx/src/executors/run-commands/run-commands.impl';
import type { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils';
import { forEachExecutorOptions } from '../executor-options-utils';
import { deleteMatchingProperties } from './plugin-migration-utils';
type PluginOptionsBuilder<T> = (targetName: string) => T; type PluginOptionsBuilder<T> = (targetName: string) => T;
type PostTargetTransformer = ( type PostTargetTransformer = (
@ -100,7 +96,6 @@ class ExecutorToPluginMigrator<T> {
for (const targetName of this.#targetAndProjectsToMigrate.keys()) { for (const targetName of this.#targetAndProjectsToMigrate.keys()) {
await this.#migrateTarget(targetName); await this.#migrateTarget(targetName);
} }
await this.#addPlugins();
} }
return this.#targetAndProjectsToMigrate; return this.#targetAndProjectsToMigrate;
} }
@ -235,91 +230,6 @@ class ExecutorToPluginMigrator<T> {
} }
} }
async #pluginRequiresIncludes(
targetName: string,
plugin: ExpandedPluginConfiguration<T>
) {
const loadedPlugin = new LoadedNxPlugin(
{
createNodesV2: this.#createNodesV2,
createNodes: this.#createNodes,
name: this.#pluginPath,
},
plugin
);
const originalResults = this.#createNodesResultsForTargets.get(targetName);
let resultsWithIncludes: ConfigurationResult;
try {
resultsWithIncludes = await retrieveProjectConfigurations(
[loadedPlugin],
this.tree.root,
this.#nxJson
);
} catch (e) {
if (e instanceof ProjectConfigurationsError) {
resultsWithIncludes = e.partialProjectConfigurationsResult;
} else {
throw e;
}
}
return !deepEqual(originalResults, resultsWithIncludes);
}
async #addPlugins() {
for (const [targetName, plugin] of this.#pluginToAddForTarget.entries()) {
const pluginOptions = this.#pluginOptionsBuilder(targetName);
const existingPlugin = this.#nxJson.plugins.find(
(plugin: ExpandedPluginConfiguration<T>) => {
if (
typeof plugin === 'string' ||
plugin.plugin !== this.#pluginPath
) {
return;
}
for (const key in plugin.options) {
if (plugin.options[key] !== pluginOptions[key]) {
return false;
}
}
return true;
}
) as ExpandedPluginConfiguration<T>;
if (existingPlugin?.include) {
// Add to the existing plugin includes
existingPlugin.include = existingPlugin.include.concat(
// Any include that is in the new plugin's include list
plugin.include.filter(
(projectPath) =>
// And is not already covered by the existing plugin's include list
!existingPlugin.include.some((pluginIncludes) =>
minimatch(projectPath, pluginIncludes, { dot: true })
)
)
);
if (!(await this.#pluginRequiresIncludes(targetName, existingPlugin))) {
delete existingPlugin.include;
}
}
if (!existingPlugin) {
if (!(await this.#pluginRequiresIncludes(targetName, plugin))) {
plugin.include = undefined;
}
this.#nxJson.plugins.push(plugin);
}
}
updateNxJson(this.tree, this.#nxJson);
}
#getTargetAndProjectsToMigrate() { #getTargetAndProjectsToMigrate() {
forEachExecutorOptions( forEachExecutorOptions(
this.tree, this.tree,
@ -403,96 +313,289 @@ class ExecutorToPluginMigrator<T> {
} }
global.NX_GRAPH_CREATION = true; global.NX_GRAPH_CREATION = true;
try {
for (const targetName of this.#targetAndProjectsToMigrate.keys()) { for (const targetName of this.#targetAndProjectsToMigrate.keys()) {
const loadedPlugin = new LoadedNxPlugin( const result = await getCreateNodesResultsForPlugin(
{ this.tree,
createNodesV2: this.#createNodesV2,
createNodes: this.#createNodes,
name: this.#pluginPath,
},
{ {
plugin: this.#pluginPath, plugin: this.#pluginPath,
options: this.#pluginOptionsBuilder(targetName), options: this.#pluginOptionsBuilder(targetName),
},
this.#pluginPath,
this.#createNodes,
this.#createNodesV2,
this.#nxJson
);
this.#createNodesResultsForTargets.set(targetName, result);
}
} finally {
global.NX_GRAPH_CREATION = false;
}
}
}
export async function migrateProjectExecutorsToPlugin<T>(
tree: Tree,
projectGraph: ProjectGraph,
pluginPath: string,
createNodesV2: CreateNodesV2<T>,
defaultPluginOptions: T,
migrations: Array<{
executors: string[];
targetPluginOptionMapper: (targetName: string) => Partial<T>;
postTargetTransformer: PostTargetTransformer;
skipProjectFilter?: SkipProjectFilter;
skipTargetFilter?: SkipTargetFilter;
}>,
specificProjectToMigrate?: string
): Promise<Map<string, Record<string, string>>> {
const projects = await migrateProjects(
tree,
projectGraph,
pluginPath,
undefined,
createNodesV2,
defaultPluginOptions,
migrations,
specificProjectToMigrate
);
return projects;
}
export async function migrateProjectExecutorsToPluginV1<T>(
tree: Tree,
projectGraph: ProjectGraph,
pluginPath: string,
createNodes: CreateNodes<T>,
defaultPluginOptions: T,
migrations: Array<{
executors: string[];
targetPluginOptionMapper: (targetName: string) => Partial<T>;
postTargetTransformer: PostTargetTransformer;
skipProjectFilter?: SkipProjectFilter;
skipTargetFilter?: SkipTargetFilter;
}>,
specificProjectToMigrate?: string
): Promise<Map<string, Record<string, string>>> {
const projects = await migrateProjects(
tree,
projectGraph,
pluginPath,
createNodes,
undefined,
defaultPluginOptions,
migrations,
specificProjectToMigrate
);
return projects;
}
async function migrateProjects<T>(
tree: Tree,
projectGraph: ProjectGraph,
pluginPath: string,
createNodes: CreateNodes<T>,
createNodesV2: CreateNodesV2<T>,
defaultPluginOptions: T,
migrations: Array<{
executors: string[];
targetPluginOptionMapper: (targetName: string) => Partial<T>;
postTargetTransformer: PostTargetTransformer;
skipProjectFilter?: SkipProjectFilter;
skipTargetFilter?: SkipTargetFilter;
}>,
specificProjectToMigrate?: string
): Promise<Map<string, Record<string, string>>> {
const projects = new Map<string, Record<string, string>>();
for (const migration of migrations) {
for (const executor of migration.executors) {
const migrator = new ExecutorToPluginMigrator(
tree,
projectGraph,
executor,
pluginPath,
migration.targetPluginOptionMapper,
migration.postTargetTransformer,
createNodes,
createNodesV2,
specificProjectToMigrate,
{
skipProjectFilter: migration.skipProjectFilter,
skipTargetFilter: migration.skipTargetFilter,
} }
); );
let projectConfigs: ConfigurationResult;
const result = await migrator.run();
// invert the result to have a map of projects to their targets
for (const [target, projectList] of result.entries()) {
for (const project of projectList) {
if (!projects.has(project)) {
projects.set(project, {});
}
projects.set(project, {
...projects.get(project),
...migration.targetPluginOptionMapper(target),
});
}
}
}
}
// apply default options
for (const [project, pluginOptions] of projects.entries()) {
projects.set(project, {
...defaultPluginOptions,
...pluginOptions,
});
}
await addPluginRegistrations(
tree,
projects,
pluginPath,
createNodes,
createNodesV2,
defaultPluginOptions,
projectGraph
);
return projects;
}
async function addPluginRegistrations<T>(
tree: Tree,
projects: Map<string, Record<string, string>>,
pluginPath: string,
createNodes: CreateNodes | undefined,
createNodesV2: CreateNodesV2 | undefined,
defaultPluginOptions: T,
projectGraph: ProjectGraph
) {
const nxJson = readNxJson(tree);
// collect createNodes results for each project before adding the plugins
const createNodesResults = new Map<string, ConfigurationResult>();
global.NX_GRAPH_CREATION = true;
try { try {
for (const [project, options] of projects.entries()) {
const projectConfigs = await getCreateNodesResultsForPlugin(
tree,
{ plugin: pluginPath, options },
pluginPath,
createNodes,
createNodesV2,
nxJson
);
createNodesResults.set(project, projectConfigs);
}
} finally {
global.NX_GRAPH_CREATION = false;
}
const arePluginIncludesRequired = async (
project: string,
pluginConfiguration: ExpandedPluginConfiguration
): Promise<boolean> => {
global.NX_GRAPH_CREATION = true;
let result: ConfigurationResult;
try {
result = await getCreateNodesResultsForPlugin(
tree,
pluginConfiguration,
pluginPath,
createNodes,
createNodesV2,
nxJson
);
} finally {
global.NX_GRAPH_CREATION = false;
}
const originalResults = createNodesResults.get(project);
return !deepEqual(originalResults, result);
};
for (const [project, options] of projects.entries()) {
const existingPlugin = nxJson.plugins?.find(
(plugin): plugin is ExpandedPluginConfiguration =>
typeof plugin !== 'string' &&
plugin.plugin === pluginPath &&
Object.keys(options).every(
(key) =>
plugin.options[key] === options[key] ||
(plugin.options[key] === undefined &&
options[key] === defaultPluginOptions[key])
)
);
const projectIncludeGlob = `${projectGraph.nodes[project].data.root}/**/*`;
if (!existingPlugin) {
nxJson.plugins ??= [];
const plugin: ExpandedPluginConfiguration = {
plugin: pluginPath,
options,
include: [projectIncludeGlob],
};
if (!(await arePluginIncludesRequired(project, plugin))) {
delete plugin.include;
}
nxJson.plugins.push(plugin);
} else if (existingPlugin.include) {
if (
!existingPlugin.include.some((include) =>
minimatch(projectIncludeGlob, include, { dot: true })
)
) {
existingPlugin.include.push(projectIncludeGlob);
if (!(await arePluginIncludesRequired(project, existingPlugin))) {
delete existingPlugin.include;
}
}
}
}
updateNxJson(tree, nxJson);
}
async function getCreateNodesResultsForPlugin(
tree: Tree,
pluginConfiguration: ExpandedPluginConfiguration,
pluginPath: string,
createNodes: CreateNodes | undefined,
createNodesV2: CreateNodesV2 | undefined,
nxJson: NxJsonConfiguration
): Promise<ConfigurationResult> {
let projectConfigs: ConfigurationResult;
try {
const plugin = new LoadedNxPlugin(
{ createNodes, createNodesV2, name: pluginPath },
pluginConfiguration
);
projectConfigs = await retrieveProjectConfigurations( projectConfigs = await retrieveProjectConfigurations(
[loadedPlugin], [plugin],
this.tree.root, tree.root,
this.#nxJson nxJson
); );
} catch (e) { } catch (e) {
if (e instanceof ProjectConfigurationsError) { if (e instanceof ProjectConfigurationsError) {
projectConfigs = e.partialProjectConfigurationsResult; projectConfigs = e.partialProjectConfigurationsResult;
} else { } else {
global.NX_GRAPH_CREATION = false;
throw e; throw e;
} }
} }
this.#createNodesResultsForTargets.set(targetName, projectConfigs); return projectConfigs;
}
global.NX_GRAPH_CREATION = false;
}
}
export async function migrateExecutorToPlugin<T>(
tree: Tree,
projectGraph: ProjectGraph,
executor: string,
pluginPath: string,
pluginOptionsBuilder: PluginOptionsBuilder<T>,
postTargetTransformer: PostTargetTransformer,
createNodes: CreateNodesV2<T>,
specificProjectToMigrate?: string,
filters?: {
skipProjectFilter?: SkipProjectFilter;
skipTargetFilter?: SkipTargetFilter;
}
): Promise<Map<string, Set<string>>> {
const migrator = new ExecutorToPluginMigrator<T>(
tree,
projectGraph,
executor,
pluginPath,
pluginOptionsBuilder,
postTargetTransformer,
undefined,
createNodes,
specificProjectToMigrate,
filters
);
return await migrator.run();
}
export async function migrateExecutorToPluginV1<T>(
tree: Tree,
projectGraph: ProjectGraph,
executor: string,
pluginPath: string,
pluginOptionsBuilder: PluginOptionsBuilder<T>,
postTargetTransformer: PostTargetTransformer,
createNodes: CreateNodes<T>,
specificProjectToMigrate?: string,
filters?: {
skipProjectFilter?: SkipProjectFilter;
skipTargetFilter?: SkipTargetFilter;
}
): Promise<Map<string, Set<string>>> {
const migrator = new ExecutorToPluginMigrator<T>(
tree,
projectGraph,
executor,
pluginPath,
pluginOptionsBuilder,
postTargetTransformer,
createNodes,
undefined,
specificProjectToMigrate,
filters
);
return await migrator.run();
} }
// Checks if two objects are structurely equal, without caring // Checks if two objects are structurely equal, without caring

View File

@ -6,7 +6,7 @@ import {
type Tree, type Tree,
} from '@nx/devkit'; } from '@nx/devkit';
import { createNodesV2, EslintPluginOptions } from '../../plugins/plugin'; import { createNodesV2, EslintPluginOptions } from '../../plugins/plugin';
import { migrateExecutorToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator'; import { migrateProjectExecutorsToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import { targetOptionsToCliMap } from './lib/target-options-map'; import { targetOptionsToCliMap } from './lib/target-options-map';
import { interpolate } from 'nx/src/tasks-runner/utils'; import { interpolate } from 'nx/src/tasks-runner/utils';
import { import {
@ -22,33 +22,24 @@ interface Schema {
export async function convertToInferred(tree: Tree, options: Schema) { export async function convertToInferred(tree: Tree, options: Schema) {
const projectGraph = await createProjectGraphAsync(); const projectGraph = await createProjectGraphAsync();
const migratedProjectsModern =
await migrateExecutorToPlugin<EslintPluginOptions>(
tree,
projectGraph,
'@nx/eslint:lint',
'@nx/eslint/plugin',
(targetName) => ({ targetName }),
postTargetTransformer,
createNodesV2,
options.project
);
const migratedProjectsLegacy =
await migrateExecutorToPlugin<EslintPluginOptions>(
tree,
projectGraph,
'@nrwl/linter:eslint',
'@nx/eslint/plugin',
(targetName) => ({ targetName }),
postTargetTransformer,
createNodesV2,
options.project
);
const migratedProjects = const migratedProjects =
migratedProjectsModern.size + migratedProjectsLegacy.size; await migrateProjectExecutorsToPlugin<EslintPluginOptions>(
if (migratedProjects === 0) { tree,
projectGraph,
'@nx/eslint/plugin',
createNodesV2,
{ targetName: 'lint' },
[
{
executors: ['@nx/eslint:lint', '@nrwl/linter:eslint'],
postTargetTransformer,
targetPluginOptionMapper: (targetName) => ({ targetName }),
},
],
options.project
);
if (migratedProjects.size === 0) {
throw new Error('Could not find any targets to migrate.'); throw new Error('Could not find any targets to migrate.');
} }

View File

@ -5,7 +5,7 @@ import {
type TargetConfiguration, type TargetConfiguration,
type Tree, type Tree,
} from '@nx/devkit'; } from '@nx/devkit';
import { migrateExecutorToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator'; import { migrateProjectExecutorsToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import { import {
processTargetOutputs, processTargetOutputs,
toProjectRelativePath, toProjectRelativePath,
@ -22,34 +22,24 @@ interface Schema {
export async function convertToInferred(tree: Tree, options: Schema) { export async function convertToInferred(tree: Tree, options: Schema) {
const projectGraph = await createProjectGraphAsync(); const projectGraph = await createProjectGraphAsync();
const migratedProjectsModern =
await migrateExecutorToPlugin<JestPluginOptions>(
tree,
projectGraph,
'@nx/jest:jest',
'@nx/jest/plugin',
(targetName) => ({ targetName }),
postTargetTransformer,
createNodesV2,
options.project
);
const migratedProjectsLegacy =
await migrateExecutorToPlugin<JestPluginOptions>(
tree,
projectGraph,
'@nrwl/jest:jest',
'@nx/jest/plugin',
(targetName) => ({ targetName }),
postTargetTransformer,
createNodesV2,
options.project
);
const migratedProjects = const migratedProjects =
migratedProjectsModern.size + migratedProjectsLegacy.size; await migrateProjectExecutorsToPlugin<JestPluginOptions>(
tree,
projectGraph,
'@nx/jest/plugin',
createNodesV2,
{ targetName: 'test' },
[
{
executors: ['@nx/jest:jest', '@nrwl/jest:jest'],
postTargetTransformer,
targetPluginOptionMapper: (targetName) => ({ targetName }),
},
],
options.project
);
if (migratedProjects === 0) { if (migratedProjects.size === 0) {
throw new Error('Could not find any targets to migrate.'); throw new Error('Could not find any targets to migrate.');
} }

View File

@ -6,7 +6,7 @@ import {
type Tree, type Tree,
} from '@nx/devkit'; } from '@nx/devkit';
import { createNodesV2, PlaywrightPluginOptions } from '../../plugins/plugin'; import { createNodesV2, PlaywrightPluginOptions } from '../../plugins/plugin';
import { migrateExecutorToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator'; import { migrateProjectExecutorsToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
interface Schema { interface Schema {
project?: string; project?: string;
@ -17,14 +17,19 @@ interface Schema {
export async function convertToInferred(tree: Tree, options: Schema) { export async function convertToInferred(tree: Tree, options: Schema) {
const projectGraph = await createProjectGraphAsync(); const projectGraph = await createProjectGraphAsync();
const migratedProjects = const migratedProjects =
await migrateExecutorToPlugin<PlaywrightPluginOptions>( await migrateProjectExecutorsToPlugin<PlaywrightPluginOptions>(
tree, tree,
projectGraph, projectGraph,
'@nx/playwright:playwright',
'@nx/playwright/plugin', '@nx/playwright/plugin',
(targetName) => ({ targetName, ciTargetName: 'e2e-ci' }),
postTargetTransformer,
createNodesV2, createNodesV2,
{ targetName: 'e2e', ciTargetName: 'e2e-ci' },
[
{
executors: ['@nx/playwright:playwright'],
postTargetTransformer,
targetPluginOptionMapper: (targetName) => ({ targetName }),
},
],
options.project options.project
); );

View File

@ -239,7 +239,7 @@ describe('Remix - Convert To Inferred', () => {
plugin: '@nx/remix/plugin', plugin: '@nx/remix/plugin',
options: { options: {
buildTargetName: 'build', buildTargetName: 'build',
devTargetName: defaultTestProjectOptions.serveTargetName, devTargetName: 'custom-dev',
startTargetName: 'start', startTargetName: 'start',
typecheckTargetName: 'typecheck', typecheckTargetName: 'typecheck',
staticServeTargetName: 'static-serve', staticServeTargetName: 'static-serve',

View File

@ -1,15 +1,9 @@
import { import { createProjectGraphAsync, formatFiles, type Tree } from '@nx/devkit';
addDependenciesToPackageJson,
createProjectGraphAsync,
formatFiles,
runTasksInSerial,
type Tree,
} from '@nx/devkit';
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
import { migrateExecutorToPluginV1 } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator'; import { migrateProjectExecutorsToPluginV1 } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import { createNodes } from '../../plugins/plugin';
import { buildPostTargetTransformer } from './lib/build-post-target-transformer'; import { buildPostTargetTransformer } from './lib/build-post-target-transformer';
import { servePostTargetTransformer } from './lib/serve-post-target-transformer'; import { servePostTargetTransformer } from './lib/serve-post-target-transformer';
import { createNodes } from '../../plugins/plugin';
interface Schema { interface Schema {
project?: string; project?: string;
@ -19,43 +13,38 @@ interface Schema {
export async function convertToInferred(tree: Tree, options: Schema) { export async function convertToInferred(tree: Tree, options: Schema) {
const projectGraph = await createProjectGraphAsync(); const projectGraph = await createProjectGraphAsync();
const migrationLogs = new AggregatedLog(); const migrationLogs = new AggregatedLog();
const migratedBuildProjects = await migrateExecutorToPluginV1( const migratedProjects = await migrateProjectExecutorsToPluginV1(
tree, tree,
projectGraph, projectGraph,
'@nx/remix:build',
'@nx/remix/plugin', '@nx/remix/plugin',
(targetName) => ({ createNodes,
buildTargetName: targetName, {
buildTargetName: 'build',
devTargetName: 'dev', devTargetName: 'dev',
startTargetName: 'start', startTargetName: 'start',
typecheckTargetName: 'typecheck',
staticServeTargetName: 'static-serve', staticServeTargetName: 'static-serve',
typecheckTargetName: 'typecheck',
},
[
{
executors: ['@nx/remix:build'],
postTargetTransformer: buildPostTargetTransformer(migrationLogs),
targetPluginOptionMapper: (targetName) => ({
buildTargetName: targetName,
}), }),
buildPostTargetTransformer(migrationLogs), },
createNodes, {
options.project executors: ['@nx/remix:serve'],
); postTargetTransformer: servePostTargetTransformer(migrationLogs),
targetPluginOptionMapper: (targetName) => ({
const migratedServeProjects = await migrateExecutorToPluginV1(
tree,
projectGraph,
'@nx/remix:serve',
'@nx/remix/plugin',
(targetName) => ({
buildTargetName: 'build',
devTargetName: targetName, devTargetName: targetName,
startTargetName: 'start',
typecheckTargetName: 'typecheck',
staticServeTargetName: 'static-serve',
}), }),
servePostTargetTransformer(migrationLogs), },
createNodes, ],
options.project options.project
); );
const migratedProjects = if (migratedProjects.size === 0) {
migratedBuildProjects.size + migratedServeProjects.size;
if (migratedProjects === 0) {
throw new Error('Could not find any targets to migrate.'); throw new Error('Could not find any targets to migrate.');
} }

View File

@ -287,8 +287,8 @@ describe('Storybook - Convert To Inferred', () => {
nxJson.plugins.push({ nxJson.plugins.push({
plugin: '@nx/storybook/plugin', plugin: '@nx/storybook/plugin',
options: { options: {
buildTargetName: 'storybook-build', buildStorybookTargetName: 'storybook-build',
serveTargetName: defaultTestProjectOptions.serveTargetName, serveStorybookTargetName: 'custom-storybook',
staticStorybookTargetName: 'static-storybook', staticStorybookTargetName: 'static-storybook',
testStorybookTargetName: 'test-storybook', testStorybookTargetName: 'test-storybook',
}, },

View File

@ -6,7 +6,7 @@ import {
type Tree, type Tree,
} from '@nx/devkit'; } from '@nx/devkit';
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
import { migrateExecutorToPluginV1 } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator'; import { migrateProjectExecutorsToPluginV1 } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import { buildPostTargetTransformer } from './lib/build-post-target-transformer'; import { buildPostTargetTransformer } from './lib/build-post-target-transformer';
import { servePostTargetTransformer } from './lib/serve-post-target-transformer'; import { servePostTargetTransformer } from './lib/serve-post-target-transformer';
import { createNodes } from '../../plugins/plugin'; import { createNodes } from '../../plugins/plugin';
@ -20,41 +20,37 @@ interface Schema {
export async function convertToInferred(tree: Tree, options: Schema) { export async function convertToInferred(tree: Tree, options: Schema) {
const projectGraph = await createProjectGraphAsync(); const projectGraph = await createProjectGraphAsync();
const migrationLogs = new AggregatedLog(); const migrationLogs = new AggregatedLog();
const migratedBuildProjects = await migrateExecutorToPluginV1( const migratedProjects = await migrateProjectExecutorsToPluginV1(
tree, tree,
projectGraph, projectGraph,
'@nx/storybook:build',
'@nx/storybook/plugin', '@nx/storybook/plugin',
(targetName) => ({ createNodes,
buildStorybookTargetName: targetName, {
buildStorybookTargetName: 'build-storybook',
serveStorybookTargetName: 'storybook', serveStorybookTargetName: 'storybook',
staticStorybookTargetName: 'static-storybook', staticStorybookTargetName: 'static-storybook',
testStorybookTargetName: 'test-storybook', testStorybookTargetName: 'test-storybook',
},
[
{
executors: ['@nx/storybook:build', '@nrwl/storybook:build'],
postTargetTransformer: buildPostTargetTransformer(migrationLogs),
targetPluginOptionMapper: (targetName) => ({
buildStorybookTargetName: targetName,
}), }),
buildPostTargetTransformer(migrationLogs), },
createNodes, {
options.project executors: ['@nx/storybook:storybook', '@nrwl/storybook:storybook'],
); postTargetTransformer: servePostTargetTransformer(migrationLogs),
targetPluginOptionMapper: (targetName) => ({
const migratedServeProjects = await migrateExecutorToPluginV1(
tree,
projectGraph,
'@nx/storybook:storybook',
'@nx/storybook/plugin',
(targetName) => ({
buildStorybookTargetName: 'build-storybook',
serveStorybookTargetName: targetName, serveStorybookTargetName: targetName,
staticStorybookTargetName: 'static-storybook',
testStorybookTargetName: 'test-storybook',
}), }),
servePostTargetTransformer(migrationLogs), },
createNodes, ],
options.project options.project
); );
const migratedProjects = if (migratedProjects.size === 0) {
migratedBuildProjects.size + migratedServeProjects.size;
if (migratedProjects === 0) {
throw new Error('Could not find any targets to migrate.'); throw new Error('Could not find any targets to migrate.');
} }

View File

@ -18,6 +18,7 @@ import {
} from '@nx/devkit'; } from '@nx/devkit';
import { TempFs } from '@nx/devkit/internal-testing-utils'; import { TempFs } from '@nx/devkit/internal-testing-utils';
import { join } from 'node:path'; import { join } from 'node:path';
import type { VitePluginOptions } from '../../plugins/plugin';
let fs: TempFs; let fs: TempFs;
@ -514,33 +515,116 @@ describe('Vite - Convert Executors To Plugin', () => {
// nx.json modifications // nx.json modifications
const nxJsonPlugins = readNxJson(tree).plugins; const nxJsonPlugins = readNxJson(tree).plugins;
const addedTestVitePlugin = nxJsonPlugins.find((plugin) => { const addedVitePlugins = nxJsonPlugins.filter((plugin) => {
if ( if (typeof plugin !== 'string' && plugin.plugin === '@nx/vite/plugin') {
typeof plugin !== 'string' &&
plugin.plugin === '@nx/vite/plugin' &&
plugin.include?.length === 2
) {
return true; return true;
} }
}); });
expect(addedTestVitePlugin).toBeTruthy(); expect(addedVitePlugins).toMatchInlineSnapshot(`
expect( [
(addedTestVitePlugin as ExpandedPluginConfiguration).include {
).toEqual(['myapp/**/*', 'second/**/*']); "options": {
"buildTargetName": "build",
"previewTargetName": "preview",
"serveTargetName": "serve",
"testTargetName": "test",
},
"plugin": "@nx/vite/plugin",
},
{
"include": [
"myapp/**/*",
"second/**/*",
],
"options": {
"buildTargetName": "bundle",
"previewTargetName": "preview",
"serveStaticTargetName": "serve-static",
"serveTargetName": "serve",
"testTargetName": "test",
},
"plugin": "@nx/vite/plugin",
},
{
"include": [
"third/**/*",
],
"options": {
"buildTargetName": "build-base",
"previewTargetName": "preview",
"serveStaticTargetName": "serve-static",
"serveTargetName": "serve",
"testTargetName": "test",
},
"plugin": "@nx/vite/plugin",
},
]
`);
});
const addedIntegrationVitePlugin = nxJsonPlugins.find((plugin) => { it('should handle multiple different target names for the same project', async () => {
if ( const project1 = createTestProject(tree);
typeof plugin !== 'string' && const project2 = createTestProject(tree, {
plugin.plugin === '@nx/vite/plugin' && appRoot: 'project2',
plugin.include?.length === 1 appName: 'project2',
) {
return true;
}
}); });
expect(addedIntegrationVitePlugin).toBeTruthy(); const project3 = createTestProject(tree, {
expect( appRoot: 'project3',
(addedIntegrationVitePlugin as ExpandedPluginConfiguration).include appName: 'project3',
).toEqual(['third/**/*']); buildTargetName: 'vite-build',
serveTargetName: 'vite-serve',
});
const project4 = createTestProject(tree, {
appRoot: 'project4',
appName: 'project4',
buildTargetName: 'build',
serveTargetName: 'vite-serve',
});
const project5 = createTestProject(tree, {
appRoot: 'project5',
appName: 'project5',
buildTargetName: 'vite-build',
serveTargetName: 'serve',
});
await convertToInferred(tree, { skipFormat: true });
// nx.json modifications
const nxJsonPlugins = readNxJson(tree).plugins;
const vitePluginRegistrations = nxJsonPlugins.filter(
(plugin): plugin is ExpandedPluginConfiguration<VitePluginOptions> =>
typeof plugin !== 'string' && plugin.plugin === '@nx/vite/plugin'
);
expect(vitePluginRegistrations.length).toBe(4);
expect(vitePluginRegistrations[0].options.buildTargetName).toBe('build');
expect(vitePluginRegistrations[0].options.serveTargetName).toBe('serve');
expect(vitePluginRegistrations[0].include).toEqual([
`${project1.root}/**/*`,
`${project2.root}/**/*`,
]);
expect(vitePluginRegistrations[1].options.buildTargetName).toBe('build');
expect(vitePluginRegistrations[1].options.serveTargetName).toBe(
'vite-serve'
);
expect(vitePluginRegistrations[1].include).toEqual([
`${project4.root}/**/*`,
]);
expect(vitePluginRegistrations[2].options.buildTargetName).toBe(
'vite-build'
);
expect(vitePluginRegistrations[2].options.serveTargetName).toBe(
'vite-serve'
);
expect(vitePluginRegistrations[2].include).toEqual([
`${project3.root}/**/*`,
]);
expect(vitePluginRegistrations[3].options.buildTargetName).toBe(
'vite-build'
);
expect(vitePluginRegistrations[3].options.serveTargetName).toBe('serve');
expect(vitePluginRegistrations[3].include).toEqual([
`${project5.root}/**/*`,
]);
}); });
it('should keep Vite options in project.json', async () => { it('should keep Vite options in project.json', async () => {

View File

@ -1,5 +1,5 @@
import { createProjectGraphAsync, formatFiles, type Tree } from '@nx/devkit'; import { createProjectGraphAsync, formatFiles, type Tree } from '@nx/devkit';
import { migrateExecutorToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator'; import { migrateProjectExecutorsToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import { createNodesV2, VitePluginOptions } from '../../plugins/plugin'; import { createNodesV2, VitePluginOptions } from '../../plugins/plugin';
import { buildPostTargetTransformer } from './lib/build-post-target-transformer'; import { buildPostTargetTransformer } from './lib/build-post-target-transformer';
import { servePostTargetTransformer } from './lib/serve-post-target-transformer'; import { servePostTargetTransformer } from './lib/serve-post-target-transformer';
@ -15,81 +15,46 @@ interface Schema {
export async function convertToInferred(tree: Tree, options: Schema) { export async function convertToInferred(tree: Tree, options: Schema) {
const projectGraph = await createProjectGraphAsync(); const projectGraph = await createProjectGraphAsync();
const migrationLogs = new AggregatedLog(); const migrationLogs = new AggregatedLog();
const migratedBuildProjects =
await migrateExecutorToPlugin<VitePluginOptions>(
tree,
projectGraph,
'@nx/vite:build',
'@nx/vite/plugin',
(targetName) => ({
buildTargetName: targetName,
serveTargetName: 'serve',
previewTargetName: 'preview',
testTargetName: 'test',
serveStaticTargetName: 'serve-static',
}),
buildPostTargetTransformer,
createNodesV2,
options.project
);
const migratedServeProjects =
await migrateExecutorToPlugin<VitePluginOptions>(
tree,
projectGraph,
'@nx/vite:dev-server',
'@nx/vite/plugin',
(targetName) => ({
buildTargetName: 'build',
serveTargetName: targetName,
previewTargetName: 'preview',
testTargetName: 'test',
serveStaticTargetName: 'serve-static',
}),
servePostTargetTransformer(migrationLogs),
createNodesV2,
options.project
);
const migratedPreviewProjects =
await migrateExecutorToPlugin<VitePluginOptions>(
tree,
projectGraph,
'@nx/vite:preview-server',
'@nx/vite/plugin',
(targetName) => ({
buildTargetName: 'build',
serveTargetName: 'serve',
previewTargetName: targetName,
testTargetName: 'test',
serveStaticTargetName: 'serve-static',
}),
previewPostTargetTransformer(migrationLogs),
createNodesV2,
options.project
);
const migratedTestProjects = await migrateExecutorToPlugin<VitePluginOptions>(
tree,
projectGraph,
'@nx/vite:test',
'@nx/vite/plugin',
(targetName) => ({
buildTargetName: 'build',
serveTargetName: 'serve',
previewTargetName: 'preview',
testTargetName: targetName,
serveStaticTargetName: 'serve-static',
}),
testPostTargetTransformer,
createNodesV2,
options.project
);
const migratedProjects = const migratedProjects =
migratedBuildProjects.size + await migrateProjectExecutorsToPlugin<VitePluginOptions>(
migratedServeProjects.size + tree,
migratedPreviewProjects.size + projectGraph,
migratedTestProjects.size; '@nx/vite/plugin',
createNodesV2,
{
buildTargetName: 'build',
serveTargetName: 'serve',
previewTargetName: 'preview',
testTargetName: 'test',
serveStaticTargetName: 'serve-static',
},
[
{
executors: ['@nx/vite:build'],
postTargetTransformer: buildPostTargetTransformer,
targetPluginOptionMapper: (target) => ({ buildTargetName: target }),
},
{
executors: ['@nx/vite:dev-server'],
postTargetTransformer: servePostTargetTransformer(migrationLogs),
targetPluginOptionMapper: (target) => ({ serveTargetName: target }),
},
{
executors: ['@nx/vite:preview-server'],
postTargetTransformer: previewPostTargetTransformer(migrationLogs),
targetPluginOptionMapper: (target) => ({ previewTargetName: target }),
},
{
executors: ['@nx/vite:test'],
postTargetTransformer: testPostTargetTransformer,
targetPluginOptionMapper: (target) => ({ testTargetName: target }),
},
],
options.project
);
if (migratedProjects === 0) { if (migratedProjects.size === 0) {
throw new Error('Could not find any targets to migrate.'); throw new Error('Could not find any targets to migrate.');
} }

View File

@ -3,6 +3,7 @@ import {
joinPathFragments, joinPathFragments,
readNxJson, readNxJson,
readProjectConfiguration, readProjectConfiguration,
updateNxJson,
updateProjectConfiguration, updateProjectConfiguration,
writeJson, writeJson,
type ExpandedPluginConfiguration, type ExpandedPluginConfiguration,
@ -14,6 +15,7 @@ import { TempFs } from '@nx/devkit/internal-testing-utils';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { join } from 'node:path'; import { join } from 'node:path';
import { getRelativeProjectJsonSchemaPath } from 'nx/src/generators/utils/project-configuration'; import { getRelativeProjectJsonSchemaPath } from 'nx/src/generators/utils/project-configuration';
import type { WebpackPluginOptions } from '../../plugins/plugin';
import { convertToInferred } from './convert-to-inferred'; import { convertToInferred } from './convert-to-inferred';
let fs: TempFs; let fs: TempFs;
@ -377,6 +379,78 @@ describe('convert-to-inferred', () => {
expect(updatedProject2.targets.build).toStrictEqual(project2BuildTarget); expect(updatedProject2.targets.build).toStrictEqual(project2BuildTarget);
}); });
it('should remove "includes" from the plugin registration when all projects are included', async () => {
const project1 = createProject(tree);
writeWebpackConfig(tree, project1.root);
const nxJson = readNxJson(tree);
nxJson.plugins ??= [];
nxJson.plugins.push({
plugin: '@nx/webpack/plugin',
options: {
buildTargetName: 'build',
previewTargetName: 'preview',
serveStaticTargetName: 'serve-static',
serveTargetName: 'serve',
},
include: [`${project1.root}/**/*`],
});
updateNxJson(tree, nxJson);
const project2 = createProject(tree, {
appName: 'app2',
appRoot: 'apps/app2',
});
writeWebpackConfig(tree, project2.root);
await convertToInferred(tree, { project: project2.name });
// nx.json modifications
const nxJsonPlugins = readNxJson(tree).plugins;
const webpackPluginRegistrations = nxJsonPlugins.filter(
(plugin): plugin is ExpandedPluginConfiguration<WebpackPluginOptions> =>
typeof plugin !== 'string' && plugin.plugin === '@nx/webpack/plugin'
);
expect(webpackPluginRegistrations.length).toBe(1);
expect(webpackPluginRegistrations[0].include).toBeUndefined();
});
it('should not add to "includes" when existing matching registration does not have it set', async () => {
const project1 = createProject(tree);
writeWebpackConfig(tree, project1.root);
const nxJson = readNxJson(tree);
nxJson.plugins ??= [];
nxJson.plugins.push({
plugin: '@nx/webpack/plugin',
options: {
buildTargetName: 'build',
previewTargetName: 'preview',
serveStaticTargetName: 'serve-static',
serveTargetName: 'serve',
},
});
updateNxJson(tree, nxJson);
const project2 = createProject(tree, {
appName: 'app2',
appRoot: 'apps/app2',
});
writeWebpackConfig(tree, project2.root);
const project3 = createProject(tree, {
appName: 'app3',
appRoot: 'apps/app3',
});
writeWebpackConfig(tree, project3.root);
await convertToInferred(tree, { project: project2.name });
// nx.json modifications
const nxJsonPlugins = readNxJson(tree).plugins;
const webpackPluginRegistrations = nxJsonPlugins.filter(
(plugin): plugin is ExpandedPluginConfiguration<WebpackPluginOptions> =>
typeof plugin !== 'string' && plugin.plugin === '@nx/webpack/plugin'
);
expect(webpackPluginRegistrations.length).toBe(1);
expect(webpackPluginRegistrations[0].include).toBeUndefined();
});
it('should move options to the webpack config file', async () => { it('should move options to the webpack config file', async () => {
const project = createProject(tree); const project = createProject(tree);
writeWebpackConfig(tree, project.root); writeWebpackConfig(tree, project.root);
@ -753,11 +827,26 @@ describe('convert-to-inferred', () => {
appName: 'app3', appName: 'app3',
appRoot: 'apps/app3', appRoot: 'apps/app3',
buildTargetName: 'build-webpack', buildTargetName: 'build-webpack',
serveTargetName: 'serve-webpack',
}); });
writeWebpackConfig(tree, project3.root); writeWebpackConfig(tree, project3.root);
const projectWithComposePlugins = createProject(tree, { const project4 = createProject(tree, {
appName: 'app4', appName: 'app4',
appRoot: 'apps/app4', appRoot: 'apps/app4',
buildTargetName: 'build',
serveTargetName: 'serve-webpack',
});
writeWebpackConfig(tree, project4.root);
const project5 = createProject(tree, {
appName: 'app5',
appRoot: 'apps/app5',
buildTargetName: 'build-webpack',
serveTargetName: 'serve',
});
writeWebpackConfig(tree, project5.root);
const projectWithComposePlugins = createProject(tree, {
appName: 'app6',
appRoot: 'apps/app6',
}); });
const projectWithComposePluginsInitialTargets = const projectWithComposePluginsInitialTargets =
projectWithComposePlugins.targets; projectWithComposePlugins.targets;
@ -783,8 +872,8 @@ module.exports = composePlugins(
initialProjectWithComposePluginsWebpackConfig initialProjectWithComposePluginsWebpackConfig
); );
const projectWithNoNxAppWebpackPlugin = createProject(tree, { const projectWithNoNxAppWebpackPlugin = createProject(tree, {
appName: 'app5', appName: 'app7',
appRoot: 'apps/app5', appRoot: 'apps/app7',
}); });
const projectWithNoNxAppWebpackPluginInitialTargets = const projectWithNoNxAppWebpackPluginInitialTargets =
projectWithNoNxAppWebpackPlugin.targets; projectWithNoNxAppWebpackPlugin.targets;
@ -829,6 +918,28 @@ module.exports = composePlugins(
}); });
const updatedProject3 = readProjectConfiguration(tree, project3.name); const updatedProject3 = readProjectConfiguration(tree, project3.name);
expect(updatedProject3.targets).toStrictEqual({ expect(updatedProject3.targets).toStrictEqual({
'build-webpack': {
configurations: { development: {}, production: {} },
defaultConfiguration: 'production',
},
'serve-webpack': {
configurations: { development: {}, production: {} },
defaultConfiguration: 'development',
},
});
const updatedProject4 = readProjectConfiguration(tree, project4.name);
expect(updatedProject4.targets).toStrictEqual({
build: {
configurations: { development: {}, production: {} },
defaultConfiguration: 'production',
},
'serve-webpack': {
configurations: { development: {}, production: {} },
defaultConfiguration: 'development',
},
});
const updatedProject5 = readProjectConfiguration(tree, project5.name);
expect(updatedProject5.targets).toStrictEqual({
'build-webpack': { 'build-webpack': {
configurations: { development: {}, production: {} }, configurations: { development: {}, production: {} },
defaultConfiguration: 'production', defaultConfiguration: 'production',
@ -882,6 +993,73 @@ module.exports = composePlugins(
expect(updatedProjectWithNoNxAppWebpackPluginWebpackConfig).toBe( expect(updatedProjectWithNoNxAppWebpackPluginWebpackConfig).toBe(
initialProjectWithNoNxAppWebpackPluginWebpackConfig initialProjectWithNoNxAppWebpackPluginWebpackConfig
); );
// nx.json modifications
const nxJsonPlugins = readNxJson(tree).plugins;
const webpackPluginRegistrations = nxJsonPlugins.filter(
(plugin): plugin is ExpandedPluginConfiguration<WebpackPluginOptions> =>
typeof plugin !== 'string' && plugin.plugin === '@nx/webpack/plugin'
);
expect(webpackPluginRegistrations.length).toBe(4);
expect(webpackPluginRegistrations[0].options.buildTargetName).toBe(
'build'
);
expect(webpackPluginRegistrations[0].options.serveTargetName).toBe(
'serve'
);
expect(webpackPluginRegistrations[0].include).toEqual([
`${project1.root}/**/*`,
`${project2.root}/**/*`,
]);
expect(webpackPluginRegistrations[1].options.buildTargetName).toBe(
'build'
);
expect(webpackPluginRegistrations[1].options.serveTargetName).toBe(
'serve-webpack'
);
expect(webpackPluginRegistrations[1].include).toEqual([
`${project4.root}/**/*`,
]);
expect(webpackPluginRegistrations[2].options.buildTargetName).toBe(
'build-webpack'
);
expect(webpackPluginRegistrations[2].options.serveTargetName).toBe(
'serve-webpack'
);
expect(webpackPluginRegistrations[2].include).toEqual([
`${project3.root}/**/*`,
]);
expect(webpackPluginRegistrations[3].options.buildTargetName).toBe(
'build-webpack'
);
expect(webpackPluginRegistrations[3].options.serveTargetName).toBe(
'serve'
);
expect(webpackPluginRegistrations[3].include).toEqual([
`${project5.root}/**/*`,
]);
});
it('should remove "includes" from the plugin registration when all projects are included', async () => {
const project1 = createProject(tree);
writeWebpackConfig(tree, project1.root);
const project2 = createProject(tree, {
appName: 'app2',
appRoot: 'apps/app2',
buildExecutor: '@nrwl/webpack:webpack',
serveExecutor: '@nrwl/webpack:dev-server',
});
writeWebpackConfig(tree, project2.root);
await convertToInferred(tree, {});
// nx.json modifications
const nxJsonPlugins = readNxJson(tree).plugins;
const webpackPluginRegistrations = nxJsonPlugins.filter(
(plugin): plugin is ExpandedPluginConfiguration<WebpackPluginOptions> =>
typeof plugin !== 'string' && plugin.plugin === '@nx/webpack/plugin'
);
expect(webpackPluginRegistrations.length).toBe(1);
expect(webpackPluginRegistrations[0].include).toBeUndefined();
}); });
it('should keep the higher "memoryLimit" value in the build configuration', async () => { it('should keep the higher "memoryLimit" value in the build configuration', async () => {

View File

@ -2,20 +2,20 @@ import {
addDependenciesToPackageJson, addDependenciesToPackageJson,
createProjectGraphAsync, createProjectGraphAsync,
formatFiles, formatFiles,
type ProjectConfiguration,
runTasksInSerial, runTasksInSerial,
type ProjectConfiguration,
type Tree, type Tree,
} from '@nx/devkit'; } from '@nx/devkit';
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util'; import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
import { migrateExecutorToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator'; import { migrateProjectExecutorsToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import { tsquery } from '@phenomnomnominal/tsquery'; import { tsquery } from '@phenomnomnominal/tsquery';
import * as ts from 'typescript'; import * as ts from 'typescript';
import { createNodesV2, type WebpackPluginOptions } from '../../plugins/plugin'; import { createNodesV2, type WebpackPluginOptions } from '../../plugins/plugin';
import { webpackCliVersion } from '../../utils/versions'; import { webpackCliVersion } from '../../utils/versions';
import { import {
buildPostTargetTransformerFactory, buildPostTargetTransformerFactory,
type MigrationContext,
servePostTargetTransformerFactory, servePostTargetTransformerFactory,
type MigrationContext,
} from './utils'; } from './utils';
interface Schema { interface Schema {
@ -31,85 +31,38 @@ export async function convertToInferred(tree: Tree, options: Schema) {
workspaceRoot: tree.root, workspaceRoot: tree.root,
}; };
// build
const migratedBuildProjects =
await migrateExecutorToPlugin<WebpackPluginOptions>(
tree,
projectGraph,
'@nx/webpack:webpack',
'@nx/webpack/plugin',
(targetName) => ({
buildTargetName: targetName,
previewTargetName: 'preview',
serveStaticTargetName: 'serve-static',
serveTargetName: 'serve',
}),
buildPostTargetTransformerFactory(migrationContext),
createNodesV2,
options.project,
{ skipProjectFilter: skipProjectFilterFactory(tree) }
);
const migratedBuildProjectsLegacy =
await migrateExecutorToPlugin<WebpackPluginOptions>(
tree,
projectGraph,
'@nrwl/webpack:webpack',
'@nx/webpack/plugin',
(targetName) => ({
buildTargetName: targetName,
previewTargetName: 'preview',
serveStaticTargetName: 'serve-static',
serveTargetName: 'serve',
}),
buildPostTargetTransformerFactory(migrationContext),
createNodesV2,
options.project,
{ skipProjectFilter: skipProjectFilterFactory(tree) }
);
// serve
const migratedServeProjects =
await migrateExecutorToPlugin<WebpackPluginOptions>(
tree,
projectGraph,
'@nx/webpack:dev-server',
'@nx/webpack/plugin',
(targetName) => ({
buildTargetName: 'build',
previewTargetName: 'preview',
serveStaticTargetName: 'serve-static',
serveTargetName: targetName,
}),
servePostTargetTransformerFactory(migrationContext),
createNodesV2,
options.project,
{ skipProjectFilter: skipProjectFilterFactory(tree) }
);
const migratedServeProjectsLegacy =
await migrateExecutorToPlugin<WebpackPluginOptions>(
tree,
projectGraph,
'@nrwl/webpack:dev-server',
'@nx/webpack/plugin',
(targetName) => ({
buildTargetName: 'build',
previewTargetName: 'preview',
serveStaticTargetName: 'serve-static',
serveTargetName: targetName,
}),
servePostTargetTransformerFactory(migrationContext),
createNodesV2,
options.project,
{ skipProjectFilter: skipProjectFilterFactory(tree) }
);
const migratedProjects = const migratedProjects =
migratedBuildProjects.size + await migrateProjectExecutorsToPlugin<WebpackPluginOptions>(
migratedBuildProjectsLegacy.size + tree,
migratedServeProjects.size + projectGraph,
migratedServeProjectsLegacy.size; '@nx/webpack/plugin',
createNodesV2,
{
buildTargetName: 'build',
previewTargetName: 'preview',
serveStaticTargetName: 'serve-static',
serveTargetName: 'serve',
},
[
{
executors: ['@nx/webpack:webpack', '@nrwl/webpack:webpack'],
postTargetTransformer:
buildPostTargetTransformerFactory(migrationContext),
targetPluginOptionMapper: (target) => ({ buildTargetName: target }),
skipProjectFilter: skipProjectFilterFactory(tree),
},
{
executors: ['@nx/webpack:dev-server', '@nrwl/webpack:dev-server'],
postTargetTransformer:
servePostTargetTransformerFactory(migrationContext),
targetPluginOptionMapper: (target) => ({ serveTargetName: target }),
skipProjectFilter: skipProjectFilterFactory(tree),
},
],
options.project
);
if (migratedProjects === 0) { if (migratedProjects.size === 0) {
throw new Error('Could not find any targets to migrate.'); throw new Error('Could not find any targets to migrate.');
} }