@nx/eslint 23.0.0-rc.4 → 23.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +1 -2
  2. package/dist/src/executors/lint/lint.impl.js +0 -5
  3. package/dist/src/executors/lint/schema.d.ts +1 -14
  4. package/dist/src/executors/lint/schema.json +1 -15
  5. package/dist/src/executors/lint/utility/eslint-utils.js +7 -5
  6. package/dist/src/generators/convert-to-flat-config/generator.js +42 -19
  7. package/dist/src/generators/convert-to-flat-config/schema.d.ts +3 -0
  8. package/dist/src/generators/convert-to-flat-config/schema.json +6 -0
  9. package/dist/src/generators/init/global-eslint-config.d.ts +1 -1
  10. package/dist/src/generators/utils/eslint-file.js +20 -28
  11. package/dist/src/generators/workspace-rule/files/__name__.spec.ts__tmpl__ +1 -7
  12. package/dist/src/generators/workspace-rule/workspace-rule.js +4 -15
  13. package/dist/src/migrations/update-23-1-0/convert-to-flat-config.d.ts +15 -0
  14. package/dist/src/migrations/update-23-1-0/convert-to-flat-config.js +283 -0
  15. package/dist/src/migrations/update-23-1-0/convert-to-flat-config.md +286 -0
  16. package/dist/src/plugins/plugin.js +8 -11
  17. package/dist/src/utils/assert-supported-eslint-version.js +0 -4
  18. package/dist/src/utils/config-file.js +1 -8
  19. package/dist/src/utils/deprecation.d.ts +0 -2
  20. package/dist/src/utils/deprecation.js +1 -11
  21. package/dist/src/utils/flat-config.d.ts +1 -0
  22. package/dist/src/utils/flat-config.js +20 -18
  23. package/dist/src/utils/resolve-eslint-class.js +11 -17
  24. package/dist/src/utils/versions.d.ts +3 -4
  25. package/dist/src/utils/versions.js +5 -16
  26. package/migrations.json +121 -0
  27. package/package.json +7 -7
package/README.md CHANGED
@@ -7,13 +7,12 @@
7
7
 
8
8
  <div style="text-align: center;">
9
9
 
10
- [![CircleCI](https://circleci.com/gh/nrwl/nx.svg?style=svg)](https://circleci.com/gh/nrwl/nx)
11
10
  [![License](https://img.shields.io/npm/l/@nx/workspace.svg?style=flat-square)]()
12
11
  [![NPM Version](https://badge.fury.io/js/nx.svg)](https://www.npmjs.com/package/nx)
13
12
  [![Semantic Release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square)]()
14
13
  [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
15
- [![Join the chat at https://gitter.im/nrwl-nx/community](https://badges.gitter.im/nrwl-nx/community.svg)](https://gitter.im/nrwl-nx/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
16
14
  [![Join us on the Official Nx Discord Server](https://img.shields.io/discord/1143497901675401286?label=discord)](https://go.nx.dev/community)
15
+ [![Nx Sandboxing](https://staging.nx.app/workspaces/62d013ea0852fe0a2df74438/sandbox-badge.svg)](https://nx.dev/docs/features/ci-features/sandboxing)
17
16
 
18
17
  </div>
19
18
 
@@ -6,7 +6,6 @@ const internal_1 = require("@nx/devkit/internal");
6
6
  const fs_1 = require("fs");
7
7
  const utils_1 = require("nx/src/tasks-runner/utils");
8
8
  const path_1 = require("path");
9
- const semver_1 = require("semver");
10
9
  const config_file_1 = require("../../utils/config-file");
11
10
  const deprecation_1 = require("../../utils/deprecation");
12
11
  const versions_1 = require("../../utils/versions");
@@ -48,10 +47,6 @@ async function run(options, context) {
48
47
  : undefined;
49
48
  const { eslint, ESLint } = await (0, eslint_utils_1.resolveAndInstantiateESLint)(eslintConfigPath, normalizedOptions, hasFlatConfig);
50
49
  (0, internal_1.assertSupportedInstalledPackageVersion)('eslint', versions_1.minSupportedEslintVersion);
51
- const installedEslintVersion = (0, internal_1.getInstalledPackageVersion)('eslint');
52
- if (installedEslintVersion && (0, semver_1.major)(installedEslintVersion) === 8) {
53
- (0, deprecation_1.warnEslintV8Deprecation)();
54
- }
55
50
  if (printConfig) {
56
51
  try {
57
52
  const fileConfig = await eslint.calculateConfigForFile(printConfig);
@@ -29,17 +29,4 @@ export interface Schema extends JsonObject {
29
29
  suppressionsLocation?: string;
30
30
  }
31
31
 
32
- type Formatter =
33
- | 'stylish'
34
- | 'compact'
35
- | 'codeframe'
36
- | 'unix'
37
- | 'visualstudio'
38
- | 'table'
39
- | 'checkstyle'
40
- | 'html'
41
- | 'jslint-xml'
42
- | 'json'
43
- | 'json-with-metadata'
44
- | 'junit'
45
- | 'tap';
32
+ type Formatter = 'stylish' | 'html' | 'json' | 'json-with-metadata';
@@ -29,21 +29,7 @@
29
29
  "default": "stylish",
30
30
  "anyOf": [
31
31
  {
32
- "enum": [
33
- "stylish",
34
- "compact",
35
- "codeframe",
36
- "unix",
37
- "visualstudio",
38
- "table",
39
- "checkstyle",
40
- "html",
41
- "jslint-xml",
42
- "json",
43
- "json-with-metadata",
44
- "junit",
45
- "tap"
46
- ]
32
+ "enum": ["stylish", "html", "json", "json-with-metadata"]
47
33
  },
48
34
  {
49
35
  "minLength": 1
@@ -11,9 +11,10 @@ async function resolveAndInstantiateESLint(eslintConfigPath, options, useFlatCon
11
11
  const ESLint = await (0, resolve_eslint_class_1.resolveESLintClass)({
12
12
  useFlatConfigOverrideVal: useFlatConfig,
13
13
  });
14
- // Use the broader legacy options shape so the v8 (legacy-only) fields assigned
15
- // below type-check. Flat-only fields (ruleFilter, suppress*) are intersected
16
- // in; they're ignored at runtime by legacy ESLint and are version-gated below.
14
+ // Use the broader legacy (eslintrc) options shape so the legacy-only fields
15
+ // assigned below type-check. The flat-only fields (ruleFilter, suppress*) are
16
+ // intersected in and set conditionally below, since LegacyESLint rejects
17
+ // unknown options.
17
18
  const eslintOptions = {
18
19
  overrideConfigFile: eslintConfigPath,
19
20
  fix: !!options.fix &&
@@ -60,8 +61,9 @@ async function resolveAndInstantiateESLint(eslintConfigPath, options, useFlatCon
60
61
  eslintOptions.reportUnusedDisableDirectives =
61
62
  options.reportUnusedDisableDirectives || undefined;
62
63
  }
63
- // pass --quiet to eslint 9+ directly: filter only errors
64
- if (options.quiet && (0, semver_1.gte)(ESLint.version, '9.0.0')) {
64
+ // `ruleFilter` is flat-config only; LegacyESLint (eslintrc) throws on unknown
65
+ // options, so only set it for flat config.
66
+ if (options.quiet && useFlatConfig) {
65
67
  eslintOptions.ruleFilter = (rule) => rule.severity === 2;
66
68
  }
67
69
  // Handle bulk suppression options (ESLint v9.24.0+)
@@ -7,9 +7,18 @@ const plugin_1 = require("../utils/plugin");
7
7
  const path_1 = require("path");
8
8
  const assert_supported_eslint_version_1 = require("../../utils/assert-supported-eslint-version");
9
9
  const versions_1 = require("../../utils/versions");
10
+ const config_file_1 = require("../../utils/config-file");
10
11
  const json_converter_1 = require("./converters/json-converter");
11
12
  async function convertToFlatConfigGenerator(tree, options) {
12
13
  (0, assert_supported_eslint_version_1.assertSupportedEslintVersion)(tree);
14
+ // Already on flat config at the root? There is nothing to convert.
15
+ const hasRootFlatConfig = [
16
+ ...config_file_1.ESLINT_FLAT_CONFIG_FILENAMES,
17
+ ...config_file_1.BASE_ESLINT_CONFIG_FILENAMES,
18
+ ].some((file) => tree.exists(file));
19
+ if (hasRootFlatConfig) {
20
+ return;
21
+ }
13
22
  const eslintFile = (0, eslint_file_1.findEslintFile)(tree);
14
23
  if (!eslintFile) {
15
24
  throw new Error('Could not find root eslint file');
@@ -20,11 +29,11 @@ async function convertToFlatConfigGenerator(tree, options) {
20
29
  options.eslintConfigFormat ??= 'mjs';
21
30
  const eslintIgnoreFiles = new Set(['.eslintignore']);
22
31
  // convert root eslint config to eslint.config.cjs or eslint.base.config.mjs based on eslintConfigFormat
23
- convertRootToFlatConfig(tree, eslintFile, options.eslintConfigFormat);
32
+ convertRootToFlatConfig(tree, eslintFile, options.eslintConfigFormat, options.keepExistingVersions);
24
33
  // convert project eslint files to eslint.config.cjs
25
34
  const projects = (0, devkit_1.getProjects)(tree);
26
35
  for (const [project, projectConfig] of projects) {
27
- convertProjectToFlatConfig(tree, project, projectConfig, (0, devkit_1.readNxJson)(tree), eslintIgnoreFiles, options.eslintConfigFormat);
36
+ convertProjectToFlatConfig(tree, project, projectConfig, (0, devkit_1.readNxJson)(tree), eslintIgnoreFiles, options.eslintConfigFormat, options.keepExistingVersions);
28
37
  }
29
38
  // delete all .eslintignore files
30
39
  for (const ignoreFile of eslintIgnoreFiles) {
@@ -40,20 +49,33 @@ async function convertToFlatConfigGenerator(tree, options) {
40
49
  return () => (0, devkit_1.installPackagesTask)(tree);
41
50
  }
42
51
  exports.default = convertToFlatConfigGenerator;
43
- function convertRootToFlatConfig(tree, eslintFile, format) {
52
+ function convertRootToFlatConfig(tree, eslintFile, format, keepExistingVersions) {
44
53
  if (/\.base\.(js|json|yml|yaml)$/.test(eslintFile)) {
45
- convertConfigToFlatConfig(tree, '', eslintFile, `eslint.base.config.${format}`, format);
54
+ convertConfigToFlatConfig(tree, '', eslintFile, `eslint.base.config.${format}`, format, undefined, keepExistingVersions);
55
+ }
56
+ // A workspace can ship `.eslintrc.base.json` without a sibling root config, so
57
+ // only convert the non-base root config when it actually exists.
58
+ const rootEslintFile = eslintFile.replace('.base.', '.');
59
+ if (tree.exists(rootEslintFile)) {
60
+ convertConfigToFlatConfig(tree, '', rootEslintFile, `eslint.config.${format}`, format, undefined, keepExistingVersions);
46
61
  }
47
- convertConfigToFlatConfig(tree, '', eslintFile.replace('.base.', '.'), `eslint.config.${format}`, format);
48
62
  }
49
63
  const ESLINT_LINT_EXECUTOR = '@nx/eslint:lint';
50
64
  function isEslintTarget(target) {
51
65
  return (target.executor === ESLINT_LINT_EXECUTOR ||
52
66
  target.command?.includes('eslint'));
53
67
  }
54
- function convertProjectToFlatConfig(tree, project, projectConfig, nxJson, eslintIgnoreFiles, format) {
68
+ function convertProjectToFlatConfig(tree, project, projectConfig, nxJson, eslintIgnoreFiles, format, keepExistingVersions) {
55
69
  const eslintFile = (0, eslint_file_1.findEslintFile)(tree, projectConfig.root);
56
- if (!eslintFile || eslintFile.endsWith('.js')) {
70
+ if (!eslintFile) {
71
+ return;
72
+ }
73
+ if (eslintFile === '.eslintrc.js' || eslintFile === '.eslintrc.cjs') {
74
+ devkit_1.logger.warn(`Skipping "${project}": ${eslintFile} is a JavaScript-based ESLint config, which cannot be converted automatically. Convert it to flat config manually.`);
75
+ return;
76
+ }
77
+ if (eslintFile.endsWith('.js')) {
78
+ // Already on a JavaScript-based flat config (eslint.config.js); nothing to convert.
57
79
  return;
58
80
  }
59
81
  // Clean up obsolete target options and detect explicit ESLint targets
@@ -83,7 +105,7 @@ function convertProjectToFlatConfig(tree, project, projectConfig, nxJson, eslint
83
105
  devkit_1.logger.warn(`Skipping "${project}": found ${eslintFile} but no ESLint lint target detected. Convert manually if needed.`);
84
106
  return;
85
107
  }
86
- convertConfigToFlatConfig(tree, projectConfig.root, eslintFile, `eslint.config.${format}`, format, ignorePath);
108
+ convertConfigToFlatConfig(tree, projectConfig.root, eslintFile, `eslint.config.${format}`, format, ignorePath, keepExistingVersions);
87
109
  eslintIgnoreFiles.add(`${projectConfig.root}/.eslintignore`);
88
110
  if (ignorePath) {
89
111
  eslintIgnoreFiles.add(ignorePath);
@@ -185,7 +207,7 @@ function updateProjectConfigsInputs(tree, format) {
185
207
  function inputsEqual(a, b) {
186
208
  return a.length === b.length && a.every((entry, i) => entry === b[i]);
187
209
  }
188
- function convertConfigToFlatConfig(tree, root, source, target, format, ignorePath) {
210
+ function convertConfigToFlatConfig(tree, root, source, target, format, ignorePath, keepExistingVersions) {
189
211
  const ignorePaths = ignorePath
190
212
  ? [ignorePath, `${root}/.eslintignore`]
191
213
  : [`${root}/.eslintignore`];
@@ -193,7 +215,7 @@ function convertConfigToFlatConfig(tree, root, source, target, format, ignorePat
193
215
  if (source.endsWith('.json') || (0, path_1.basename)(source) === '.eslintrc') {
194
216
  const config = (0, devkit_1.readJson)(tree, `${root}/${source}`);
195
217
  const conversionResult = (0, json_converter_1.convertEslintJsonToFlatConfig)(tree, root, config, ignorePaths, format);
196
- return processConvertedConfig(tree, root, source, target, conversionResult);
218
+ return processConvertedConfig(tree, root, source, target, conversionResult, keepExistingVersions);
197
219
  }
198
220
  if (source.endsWith('.yaml') || source.endsWith('.yml')) {
199
221
  const originalContent = tree.read(`${root}/${source}`, 'utf-8');
@@ -203,18 +225,17 @@ function convertConfigToFlatConfig(tree, root, source, target, format, ignorePat
203
225
  filename: source,
204
226
  });
205
227
  const conversionResult = (0, json_converter_1.convertEslintJsonToFlatConfig)(tree, root, config, ignorePaths, format);
206
- return processConvertedConfig(tree, root, source, target, conversionResult);
228
+ return processConvertedConfig(tree, root, source, target, conversionResult, keepExistingVersions);
207
229
  }
208
230
  }
209
- function processConvertedConfig(tree, root, source, target, { content, addESLintRC, addESLintJS, }) {
231
+ function processConvertedConfig(tree, root, source, target, { content, addESLintRC, addESLintJS, }, keepExistingVersions) {
210
232
  // remove original config file
211
233
  tree.delete((0, path_1.join)(root, source));
212
234
  // save new
213
235
  tree.write((0, path_1.join)(root, target), content);
214
- // Once converted to flat config, the workspace is on the v9 ESLint stack —
215
- // install the latest typescript-eslint v8 lane explicitly rather than
216
- // routing through `versions(tree)` which would pick the legacy lane based
217
- // on the pre-conversion `eslintrc` workspace state.
236
+ // Once converted to flat config, the workspace should use the latest ESLint
237
+ // stack. Install the versions directly instead of routing through
238
+ // `versions(tree)`, which keys off the pre-conversion declared ESLint version.
218
239
  const devDependencies = {
219
240
  eslint: versions_1.eslintVersion,
220
241
  'eslint-config-prettier': versions_1.eslintConfigPrettierVersion,
@@ -235,7 +256,9 @@ function processConvertedConfig(tree, root, source, target, { content, addESLint
235
256
  if (addESLintJS) {
236
257
  devDependencies['@eslint/js'] = versions_1.eslintVersion;
237
258
  }
238
- // Convert-to-flat-config is an opt-in "upgrade" we intentionally overwrite
239
- // existing pins to land the workspace on the latest flat-config-ready stack.
240
- (0, devkit_1.addDependenciesToPackageJson)(tree, {}, devDependencies);
259
+ // Direct invocation is an opt-in upgrade, so by default existing pins are
260
+ // overwritten to land the workspace on the latest flat-config-ready stack.
261
+ // Migrations pass `keepExistingVersions` so the version bump stays owned by
262
+ // `packageJsonUpdates` and only newly added packages are installed here.
263
+ (0, devkit_1.addDependenciesToPackageJson)(tree, {}, devDependencies, 'package.json', keepExistingVersions);
241
264
  }
@@ -2,4 +2,7 @@ export interface ConvertToFlatConfigGeneratorSchema {
2
2
  skipFormat?: boolean;
3
3
  // Internal option
4
4
  eslintConfigFormat?: 'mjs' | 'cjs';
5
+ // Internal option. When true, existing ESLint-related pins in package.json are
6
+ // preserved and only newly added packages are installed.
7
+ keepExistingVersions?: boolean;
5
8
  }
@@ -17,6 +17,12 @@
17
17
  "enum": ["mjs", "cjs"],
18
18
  "default": "mjs",
19
19
  "x-priority": "internal"
20
+ },
21
+ "keepExistingVersions": {
22
+ "type": "boolean",
23
+ "description": "Keep the existing versions of any ESLint-related packages already declared in `package.json` instead of overwriting them. Only newly added packages are installed at the latest supported versions.",
24
+ "default": false,
25
+ "x-priority": "internal"
20
26
  }
21
27
  },
22
28
  "additionalProperties": false,
@@ -26,4 +26,4 @@ export declare const javaScriptOverride: {
26
26
  rules: {};
27
27
  };
28
28
  export declare const getGlobalEsLintConfiguration: (unitTestRunner?: string, rootProject?: boolean) => Linter.LegacyConfig;
29
- export declare const getGlobalFlatEslintConfiguration: (format: "cjs" | "mjs", rootProject?: boolean) => string;
29
+ export declare const getGlobalFlatEslintConfiguration: (format: 'cjs' | 'mjs', rootProject?: boolean) => string;
@@ -310,36 +310,28 @@ function addExtendsToLintConfig(tree, root, plugin, insertAtTheEnd = false) {
310
310
  ? 'cjs'
311
311
  : 'mjs';
312
312
  let shouldImportEslintCompat = false;
313
- // assume eslint major is 9 if not found, as it's what we'd be generating by default
314
- const eslintMajor = (0, versions_1.getInstalledEslintMajorVersion)(tree) ?? 9;
315
- if (eslintMajor >= 9) {
316
- // eslint v9 requires the incompatible plugins to be wrapped with a helper from @eslint/compat
317
- const plugins = (Array.isArray(plugin) ? plugin : [plugin]).map((p) => typeof p === 'string' ? { name: p, needCompatFixup: false } : p);
318
- let compatiblePluginsBatch = [];
319
- plugins.forEach(({ name, needCompatFixup }) => {
320
- if (needCompatFixup) {
321
- if (compatiblePluginsBatch.length > 0) {
322
- // flush the current batch of compatible plugins and reset it
323
- pluginExtends.push((0, ast_utils_1.generatePluginExtendsElement)(compatiblePluginsBatch));
324
- compatiblePluginsBatch = [];
325
- }
326
- // generate the extends for the incompatible plugin
327
- pluginExtends.push((0, ast_utils_1.generatePluginExtendsElementWithCompatFixup)(name));
328
- shouldImportEslintCompat = true;
313
+ // eslint v9 requires the incompatible plugins to be wrapped with a helper from @eslint/compat
314
+ const plugins = (Array.isArray(plugin) ? plugin : [plugin]).map((p) => typeof p === 'string' ? { name: p, needCompatFixup: false } : p);
315
+ let compatiblePluginsBatch = [];
316
+ plugins.forEach(({ name, needCompatFixup }) => {
317
+ if (needCompatFixup) {
318
+ if (compatiblePluginsBatch.length > 0) {
319
+ // flush the current batch of compatible plugins and reset it
320
+ pluginExtends.push((0, ast_utils_1.generatePluginExtendsElement)(compatiblePluginsBatch));
321
+ compatiblePluginsBatch = [];
329
322
  }
330
- else {
331
- // add the compatible plugin to the current batch
332
- compatiblePluginsBatch.push(name);
333
- }
334
- });
335
- if (compatiblePluginsBatch.length > 0) {
336
- // flush the batch of compatible plugins
337
- pluginExtends.push((0, ast_utils_1.generatePluginExtendsElement)(compatiblePluginsBatch));
323
+ // generate the extends for the incompatible plugin
324
+ pluginExtends.push((0, ast_utils_1.generatePluginExtendsElementWithCompatFixup)(name));
325
+ shouldImportEslintCompat = true;
338
326
  }
339
- }
340
- else {
341
- const plugins = (Array.isArray(plugin) ? plugin : [plugin]).map((p) => typeof p === 'string' ? p : p.name);
342
- pluginExtends.push((0, ast_utils_1.generatePluginExtendsElement)(plugins));
327
+ else {
328
+ // add the compatible plugin to the current batch
329
+ compatiblePluginsBatch.push(name);
330
+ }
331
+ });
332
+ if (compatiblePluginsBatch.length > 0) {
333
+ // flush the batch of compatible plugins
334
+ pluginExtends.push((0, ast_utils_1.generatePluginExtendsElement)(compatiblePluginsBatch));
343
335
  }
344
336
  let content = tree.read(fileName, 'utf8');
345
337
  if (shouldImportEslintCompat) {
@@ -1,4 +1,4 @@
1
- <% if (useFlatRuleTester) { %>import { RuleTester } from '@typescript-eslint/rule-tester';
1
+ import { RuleTester } from '@typescript-eslint/rule-tester';
2
2
  import type { RuleTesterConfig } from '@typescript-eslint/rule-tester';
3
3
  import { rule, RULE_NAME } from './<%= name %>';
4
4
 
@@ -7,13 +7,7 @@ const ruleTester = new RuleTester({
7
7
  parser: require('@typescript-eslint/parser'),
8
8
  },
9
9
  } as RuleTesterConfig);
10
- <% } else { %>import { TSESLint } from '@typescript-eslint/utils';
11
- import { rule, RULE_NAME } from './<%= name %>';
12
10
 
13
- const ruleTester = new TSESLint.RuleTester({
14
- parser: require.resolve('@typescript-eslint/parser'),
15
- });
16
- <% } %>
17
11
  ruleTester.run(RULE_NAME, rule, {
18
12
  valid: [`const example = true;`],
19
13
  invalid: [],
@@ -5,26 +5,18 @@ const tslib_1 = require("tslib");
5
5
  const internal_1 = require("@nx/devkit/internal");
6
6
  const devkit_1 = require("@nx/devkit");
7
7
  const path_1 = require("path");
8
- const semver_1 = require("semver");
9
8
  const ts = tslib_1.__importStar(require("typescript"));
10
9
  const workspace_lint_rules_1 = require("../../utils/workspace-lint-rules");
11
10
  const workspace_rules_project_1 = require("../workspace-rules-project/workspace-rules-project");
12
11
  const assert_supported_eslint_version_1 = require("../../utils/assert-supported-eslint-version");
13
- const flat_config_1 = require("../../utils/flat-config");
14
12
  const versions_1 = require("../../utils/versions");
15
13
  async function lintWorkspaceRuleGenerator(tree, options) {
16
14
  (0, assert_supported_eslint_version_1.assertSupportedEslintVersion)(tree);
17
15
  const tasks = [];
18
- const flatConfig = (0, flat_config_1.useFlatConfig)(tree);
19
16
  // ESLint v9 dropped the eslintrc-style `RuleTester` API. typescript-eslint's
20
- // recommended replacement for any v9 workspace (flat or eslintrc) is the
21
- // separate `@typescript-eslint/rule-tester` package, which has a flat-style
22
- // API that works with ESLint v8.57+ and v9 alike. We resolve the effective
23
- // major from `versions(tree)` to cover both declared workspaces and fresh
24
- // installs that will be bumped to v9.
25
- const { eslintVersion, typescriptESLintVersion } = (0, versions_1.versions)(tree);
26
- const effectiveEslintMajor = (0, semver_1.major)((0, semver_1.coerce)(eslintVersion));
27
- const useFlatRuleTester = flatConfig || effectiveEslintMajor >= 9;
17
+ // recommended replacement is the separate `@typescript-eslint/rule-tester`
18
+ // package, whose flat-style API works for both flat and eslintrc workspaces.
19
+ const { typescriptESLintVersion } = (0, versions_1.versions)(tree);
28
20
  const nxJson = (0, devkit_1.readNxJson)(tree);
29
21
  // Ensure that the workspace rules project has been created
30
22
  tasks.push(await (0, workspace_rules_project_1.lintWorkspaceRulesProjectGenerator)(tree, {
@@ -32,15 +24,12 @@ async function lintWorkspaceRuleGenerator(tree, options) {
32
24
  addPlugin: process.env.NX_ADD_PLUGINS !== 'false' &&
33
25
  nxJson.useInferencePlugins !== false,
34
26
  }));
35
- if (useFlatRuleTester) {
36
- tasks.push((0, devkit_1.addDependenciesToPackageJson)(tree, {}, { '@typescript-eslint/rule-tester': typescriptESLintVersion }, undefined, true));
37
- }
27
+ tasks.push((0, devkit_1.addDependenciesToPackageJson)(tree, {}, { '@typescript-eslint/rule-tester': typescriptESLintVersion }, undefined, true));
38
28
  const ruleDir = (0, devkit_1.joinPathFragments)(workspace_lint_rules_1.workspaceLintPluginDir, options.directory ?? '');
39
29
  // Generate the required files for the new rule
40
30
  (0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, 'files'), ruleDir, {
41
31
  tmpl: '',
42
32
  name: options.name,
43
- useFlatRuleTester,
44
33
  });
45
34
  const nameCamelCase = (0, internal_1.camelize)(options.name);
46
35
  /**
@@ -0,0 +1,15 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ /**
3
+ * Hybrid migration paired with `convert-to-flat-config.md`. The deterministic
4
+ * half reuses the `@nx/eslint:convert-to-flat-config` generator to convert
5
+ * JSON/YAML eslintrc configs to flat config (the version bump is owned by
6
+ * `packageJsonUpdates`, so it runs with `keepExistingVersions`). It then returns
7
+ * `agentContext` describing the work the generator could not do deterministically
8
+ * (JavaScript-based configs, removed output formatters, the passing-state
9
+ * baseline) so the paired prompt's agent can finish the job and keep the
10
+ * workspace lint-passing.
11
+ */
12
+ export default function update(tree: Tree): Promise<{
13
+ agentContext: string[];
14
+ nextSteps: string[];
15
+ } | void>;