Skip to content

Commit

Permalink
feat(core): add metagenerator for convert-to-inferred (#27672)
Browse files Browse the repository at this point in the history
<!-- 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
The DX to migrate to project crystal isn't great right now - people have
to run `convert-to-inferred` generators manually.

## Expected Behavior
We should provide a smoother way to migrate, so we introduced an
`@nx/workspace:infer-targets` generator that can handle multiple
conversions at once, either for the whole workspace or at a project
level.

---------

Co-authored-by: Jack Hsu <[email protected]>
  • Loading branch information
MaxKless and jaysoo authored Sep 13, 2024
1 parent 7232b39 commit 5bbaffb
Show file tree
Hide file tree
Showing 25 changed files with 539 additions and 43 deletions.
8 changes: 8 additions & 0 deletions docs/generated/manifests/menus.json
Original file line number Diff line number Diff line change
Expand Up @@ -10391,6 +10391,14 @@
"children": [],
"isExternal": false,
"disableCollapsible": false
},
{
"id": "infer-targets",
"path": "/nx-api/workspace/generators/infer-targets",
"name": "infer-targets",
"children": [],
"isExternal": false,
"disableCollapsible": false
}
],
"isExternal": false,
Expand Down
9 changes: 9 additions & 0 deletions docs/generated/manifests/nx-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -3487,6 +3487,15 @@
"originalFilePath": "/packages/workspace/src/generators/ci-workflow/schema.json",
"path": "/nx-api/workspace/generators/ci-workflow",
"type": "generator"
},
"/nx-api/workspace/generators/infer-targets": {
"description": "Convert Nx projects to use inferred targets.",
"file": "generated/packages/workspace/generators/infer-targets.json",
"hidden": false,
"name": "infer-targets",
"originalFilePath": "/packages/workspace/src/generators/infer-targets/schema.json",
"path": "/nx-api/workspace/generators/infer-targets",
"type": "generator"
}
},
"path": "/nx-api/workspace"
Expand Down
9 changes: 9 additions & 0 deletions docs/generated/packages-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -3451,6 +3451,15 @@
"originalFilePath": "/packages/workspace/src/generators/ci-workflow/schema.json",
"path": "workspace/generators/ci-workflow",
"type": "generator"
},
{
"description": "Convert Nx projects to use inferred targets.",
"file": "generated/packages/workspace/generators/infer-targets.json",
"hidden": false,
"name": "infer-targets",
"originalFilePath": "/packages/workspace/src/generators/infer-targets/schema.json",
"path": "workspace/generators/infer-targets",
"type": "generator"
}
],
"githubRoot": "https://github.com/nrwl/nx/blob/master",
Expand Down
35 changes: 35 additions & 0 deletions docs/generated/packages/workspace/generators/infer-targets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "infer-targets",
"factory": "./src/generators/infer-targets/infer-targets",
"schema": {
"$schema": "https://json-schema.org/schema",
"$id": "InferTargets",
"title": "",
"type": "object",
"description": "Convert Nx projects to use inferred targets.",
"properties": {
"project": {
"type": "string",
"description": "The project to convert to use inferred targets.",
"x-priority": "important"
},
"plugins": {
"type": "array",
"description": "The plugins used to infer targets. For example @nx/eslint or @nx/jest",
"items": { "type": "string" }
},
"skipFormat": {
"type": "boolean",
"description": "Whether to format files.",
"default": false
}
},
"presets": []
},
"description": "Convert Nx projects to use inferred targets.",
"implementation": "/packages/workspace/src/generators/infer-targets/infer-targets.ts",
"aliases": [],
"hidden": false,
"path": "/packages/workspace/src/generators/infer-targets/schema.json",
"type": "generator"
}
16 changes: 13 additions & 3 deletions docs/shared/recipes/running-tasks/convert-to-inferred.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,17 @@ For the best experience, we recommend that you [migrate](/features/automate-upda
npx nx migrate latest
```

## Migrate a Plugin
## Migrate All Plugins

You can use the `infer-targets` generator to quickly migrate all available plugins to use inferred tasks. See the sections below for more details on the individual plugins' migration processes.

```shell
npx nx g infer-targets
```

The generator will automatically detect all available `convert-to-inferred` generators and run the ones you choose. If you only want to try it on a single project, pass the `--project` option.

## Migrate a Single Plugin

Most of the official plugins come with a `convert-to-inferred` generator. This generator will

Expand All @@ -42,7 +52,7 @@ None of the above
For third-party plugins that provide `convert-to-inferred` generators, you should pick the `None of the above` option and type in the name of the package manually. Alternatively, you can also provide the package explicitly with `nx g <plugin>:convert-to-inferred`.
{% /callout %}

We recommend that you migrate the plugins one at a time, and check that the configurations are correct before continuing to the next plugin. If you only want to try it on a single project, pass the `--project` option.
We recommend that you check that the configurations are correct before continuing to the next plugin. If you only want to try it on a single project, pass the `--project` option.

## Understand the Migration Process

Expand Down Expand Up @@ -96,7 +106,7 @@ For example, if we migrated the `@nx/vite` plugin for a single app (i.e. `nx g @
You'll notice that the `serve` and `build` tasks are running the [Vite CLI](https://vitejs.dev/guide/cli.html) and there are no references to Nx executors. Since the targets directly invoke the Vite CLI, any options that may be passed to it can be passed via Nx commands. e.g. `nx serve demo --cors --port 8888` enables CORs and uses port `8888` using [Vite CLI options](https://vitejs.dev/guide/cli.html#options)
The same CLI setup applies to other plugins as well.

- `@nx/cypess` calls the [Cypress CLI](https://docs.cypress.io/guides/guides/command-line)
- `@nx/cypress` calls the [Cypress CLI](https://docs.cypress.io/guides/guides/command-line)
- `@nx/playwright` calls the [Playwright CLI](https://playwright.dev/docs/test-cli)
- `@nx/webpack` calls the [Webpack CLI](https://webpack.js.org/api/cli/)
- etc.
Expand Down
1 change: 1 addition & 0 deletions docs/shared/reference/sitemap.md
Original file line number Diff line number Diff line change
Expand Up @@ -755,3 +755,4 @@
- [fix-configuration](/nx-api/workspace/generators/fix-configuration)
- [npm-package](/nx-api/workspace/generators/npm-package)
- [ci-workflow](/nx-api/workspace/generators/ci-workflow)
- [infer-targets](/nx-api/workspace/generators/infer-targets)
139 changes: 139 additions & 0 deletions e2e/nx/src/workspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,150 @@ import {
tmpProjPath,
uniq,
updateFile,
updateJson,
} from '@nx/e2e/utils';
import { join } from 'path';

let proj: string;

describe('@nx/workspace:infer-targets', () => {
beforeEach(() => {
proj = newProject({
packages: ['@nx/playwright', '@nx/remix', '@nx/eslint', '@nx/jest'],
});
});

afterEach(() => cleanupProject());

it('should run or skip conversions depending on whether executors are present', async () => {
// default case, everything is generated with crystal, everything should be skipped
const remixApp = uniq('remix');
runCLI(
`generate @nx/remix:app ${remixApp} --dir apps --unitTestRunner jest --e2eTestRunner=playwright --projectNameAndDirectoryFormat=as-provided --no-interactive`
);

const output = runCLI(`generate infer-targets --no-interactive`);

expect(output).toContain('@nx/remix:convert-to-inferred - Skipped');
expect(output).toContain('@nx/playwright:convert-to-inferred - Skipped');
expect(output).toContain('@nx/eslint:convert-to-inferred - Skipped');
expect(output).toContain('@nx/jest:convert-to-inferred - Skipped');

// if we make sure there are executors to convert, conversions will run
updateJson('nx.json', (json) => {
json.plugins = [];
return json;
});

updateJson(join('apps', remixApp, 'project.json'), (json) => {
json.targets = {
build: {
executor: '@nx/remix:build',
},
lint: {
executor: '@nx/eslint:lint',
},
};
return json;
});

const output2 = runCLI(`generate infer-targets --no-interactive`);

expect(output2).toContain('@nx/remix:convert-to-inferred - Success');
expect(output2).toContain('@nx/eslint:convert-to-inferred - Success');
});

it('should run or skip only specific conversions if --plugins is passed', async () => {
// default case, everything is generated with crystal, relevant plugins should be skipped
const remixApp = uniq('remix');
runCLI(
`generate @nx/remix:app ${remixApp} --dir apps --unitTestRunner jest --e2eTestRunner=playwright --projectNameAndDirectoryFormat=as-provided --no-interactive`
);

const output = runCLI(
`generate infer-targets --plugins=@nx/eslint,@nx/jest --no-interactive`
);

expect(output).toContain('@nx/eslint:convert-to-inferred - Skipped');
expect(output).toContain('@nx/jest:convert-to-inferred - Skipped');

expect(output).not.toContain('@nx/remix');
expect(output).not.toContain('@nx/playwright');

// if we make sure there are executors to convert, relevant conversions will run
updateJson('nx.json', (json) => {
json.plugins = [];
return json;
});

updateJson(join('apps', remixApp, 'project.json'), (json) => {
json.targets = {
build: {
executor: '@nx/remix:build',
},
lint: {
executor: '@nx/eslint:lint',
},
};
return json;
});

const output2 = runCLI(
`generate infer-targets --plugins=@nx/remix,@nx/eslint --no-interactive`
);

expect(output2).toContain('@nx/remix:convert-to-inferred - Success');
expect(output2).toContain('@nx/eslint:convert-to-inferred - Success');

expect(output2).not.toContain('@nx/jest');
expect(output2).not.toContain('@nx/playwright');
});

it('should run only specific conversions for a specific project if --project is passed', async () => {
// even if we make sure there are executors for remix & remix-e2e, only remix conversions will run with --project option
const remixApp = uniq('remix');
runCLI(
`generate @nx/remix:app ${remixApp} --dir apps --unitTestRunner jest --e2eTestRunner=playwright --projectNameAndDirectoryFormat=as-provided --no-interactive`
);

updateJson('nx.json', (json) => {
json.plugins = [];
return json;
});

updateJson(join('apps', remixApp, 'project.json'), (json) => {
json.targets = {
build: {
executor: '@nx/remix:build',
},
lint: {
executor: '@nx/eslint:lint',
},
};
return json;
});

updateJson(join('apps', `${remixApp}-e2e`, 'project.json'), (json) => {
json.targets = {
e2e: {
executor: '@nx/playwright:playwright',
},
};
return json;
});

const output2 = runCLI(
`generate infer-targets --project ${remixApp} --no-interactive`
);

expect(output2).toContain('@nx/remix:convert-to-inferred - Success');
expect(output2).toContain('@nx/eslint:convert-to-inferred - Success');

expect(output2).toContain('@nx/jest:convert-to-inferred - Skipped');
expect(output2).toContain('@nx/playwright:convert-to-inferred - Skipped');
});
});

describe('@nx/workspace:convert-to-monorepo', () => {
beforeEach(() => {
proj = newProject({ packages: ['@nx/react', '@nx/js'] });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import {
type TargetConfiguration,
type Tree,
} from '@nx/devkit';
import { migrateProjectExecutorsToPlugin } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import {
migrateProjectExecutorsToPlugin,
NoTargetsToMigrateError,
} from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import {
processTargetOutputs,
toProjectRelativePath,
Expand Down Expand Up @@ -46,7 +49,7 @@ export async function convertToInferred(tree: Tree, options: Schema) {
);

if (migratedProjects.size === 0) {
throw new Error('Could not find any targets to migrate.');
throw new NoTargetsToMigrateError();
}

if (!options.skipFormat) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import {
updateNxJson,
} from '@nx/devkit';
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
import { migrateProjectExecutorsToPluginV1 } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import {
migrateProjectExecutorsToPluginV1,
NoTargetsToMigrateError,
} from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
import { createNodes } from '../../plugins/plugin';
import { processBuildOptions } from './lib/process-build-options';
import { postTargetTransformer } from './lib/post-target-transformer';
Expand Down Expand Up @@ -97,7 +100,7 @@ export async function convertToInferred(tree: Tree, options: Schema) {
updateNxJson(tree, nxJson);

if (migratedProjects.size === 0) {
throw new Error('Could not find any targets to migrate.');
throw new NoTargetsToMigrateError();
}

if (!options.skipFormat) {
Expand Down
Loading

0 comments on commit 5bbaffb

Please sign in to comment.