Repository: ember-cli/ember-cli
Pull request #10516 (2024-10-10 18:54:27)

Use ESLint 9 and Flat Config
Notes:
- PR https://github.com/ember-cli/ember-cli/pull/10521 is in this PR -- for sanity.
- includes gjs / gts out of the box (but template-imports not added -- that's for here: https://github.com/ember-cli/ember-cli/pull/10447 )
  - js config copied from https://github.com/embroider-build/app-blueprint
  - ts config copied from https://github.com/embroider-build/app-blueprint/pull/80 
- is otherwise the same, afaict
- Files are split, and does _not_ have a bunch of "if ts else js" conditions, because those have proven annoying to maintain, and something the v2 app blueprint exploration has proven out is that separate folders (js, shared, ts) to be merged later is actually very nice -- see for example: https://github.com/embroider-build/app-blueprint/pull/80
  This _does_ mean there is a bit of duplication, but I think the tradeoff is worth it.
  - To get this to work, I had to thread `options` through a couple methods in the blueprint and then add a `.filter(Boolean)` to filter out "null" (files not applicable based on options).
  - This couldn't use the same technique as the other TS files, because there are two hardcoded, and irrelevant features we have so far:
    - Delete files by name, when typescript
    - Convert JS to TS
    
    And what we want here is "conditionally rename files based on options, deleting the conditionally irrelevant variant" -- if you look in buildFileInfo, you'll see -- the logic is pretty easy

bertdeblock: You can do the file filtering [here](https://github.com/ember-cli/ember-cli/blob/ca8cd79b3d8b56b07576d9dfe60ca417f777ac3a/blueprints/app/index.js#L99-L103), and avoid all the additional code changes? Some of those methods are public API.
NullVoxPopuli: > Maybe the same can be achieved using the mapping functionality => 

File map doesn't let you do canditional mapping

Patch:
diff --git a/blueprints/addon/files/npmignore b/blueprints/addon/files/npmignore
index 8c9fd977f..8c51744de 100644
--- a/blueprints/addon/files/npmignore
+++ b/blueprints/addon/files/npmignore
@@ -7,8 +7,6 @@
 /.ember-cli
 /.env*
 /.eslintcache
-/.eslintignore
-/.eslintrc.js
 /.git/
 /.github/
 /.gitignore
@@ -21,6 +19,7 @@
 /.watchmanconfig
 /CONTRIBUTING.md
 /ember-cli-build.js
+/eslint.config.mjs
 /testem.js
 /tests/
 /tsconfig.declarations.json
diff --git a/blueprints/addon/index.js b/blueprints/addon/index.js
index f97c707fb..ac2ed3678 100644
--- a/blueprints/addon/index.js
+++ b/blueprints/addon/index.js
@@ -112,7 +112,50 @@ module.exports = {
     return stringifyAndNormalize(sortPackageJson(contents));
   },
 
-  buildFileInfo(intoDir, templateVariables, file) {
+  /**
+   * @override
+   *
+   * This modification of buildFileInfo allows our differing
+   * input files to output to a single file, depending on the options.
+   * For example:
+   *
+   *   for javascript,
+   *     _ts_eslint.config.mjs is deleted
+   *     _js_eslint.config.mjs is renamed to eslint.config.mjs
+   *
+   *   for typescript,
+   *     _js_eslint.config.mjs is deleted
+   *     _ts_eslint.config.mjs is renamed to eslint.config.mjs
+   */
+  buildFileInfo(intoDir, templateVariables, file, commandOptions) {
+    if (file.startsWith('_js_') || file.startsWith('_ts_')) {
+      let fileInfo = this._super.buildFileInfo.apply(this, arguments);
+
+      if (file.includes('_js_')) {
+        if (commandOptions.typescript) {
+          return null;
+        }
+
+        fileInfo.outputBasePath = fileInfo.outputPath.replace('_js_', '');
+        fileInfo.outputPath = fileInfo.outputPath.replace('_js_', '');
+        fileInfo.displayPath = fileInfo.outputPath.replace('_js_', '');
+        return fileInfo;
+      }
+
+      if (file.includes('_ts_')) {
+        if (!commandOptions.typescript) {
+          return null;
+        }
+
+        fileInfo.outputBasePath = fileInfo.outputPath.replace('_ts_', '');
+        fileInfo.outputPath = fileInfo.outputPath.replace('_ts_', '');
+        fileInfo.displayPath = fileInfo.outputPath.replace('_ts_', '');
+        return fileInfo;
+      }
+
+      return fileInfo;
+    }
+
     let mappedPath = this.mapFile(file, templateVariables);
     let options = {
       action: 'write',
diff --git a/blueprints/app/files/.eslintignore b/blueprints/app/files/.eslintignore
deleted file mode 100644
index 768fab33b..000000000
--- a/blueprints/app/files/.eslintignore
+++ /dev/null
@@ -1,14 +0,0 @@
-# unconventional js
-/blueprints/*/files/
-
-# compiled output
-/declarations/
-/dist/
-
-# misc
-/coverage/
-!.*
-.*/
-
-# ember-try
-/.node_modules.ember-try/
diff --git a/blueprints/app/files/.eslintrc.js b/blueprints/app/files/.eslintrc.js
deleted file mode 100644
index 42c9dfc20..000000000
--- a/blueprints/app/files/.eslintrc.js
+++ /dev/null
@@ -1,67 +0,0 @@
-'use strict';
-
-module.exports = {
-  root: true,
-  parser: '<%= typescript ? '@typescript-eslint/parser' : '@babel/eslint-parser' %>',
-  parserOptions: {
-    ecmaVersion: 'latest',<% if (!typescript) { %>
-    sourceType: 'module',
-    requireConfigFile: false,
-    babelOptions: {
-      plugins: [
-        ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }],
-      ],
-    },<% } %>
-  },
-  plugins: ['ember'<% if (typescript) { %>, '@typescript-eslint'<% } %>],
-  extends: [
-    'eslint:recommended',
-    'plugin:ember/recommended',
-    'plugin:prettier/recommended',
-  ],
-  env: {
-    browser: true,
-  },
-  rules: {},
-  overrides: [
-<% if (typescript) { %>    // ts files
-    {
-      files: ['**/*.ts'],
-      extends: [
-        'plugin:@typescript-eslint/eslint-recommended',
-        'plugin:@typescript-eslint/recommended',
-      ],
-      rules: {},
-    },
-<% } %>    // node files
-    {
-      files: [
-        './.eslintrc.js',
-        './.prettierrc.js',
-        './.stylelintrc.js',
-        './.template-lintrc.js',
-        './ember-cli-build.js',<% if (blueprint !== 'app') { %>
-        './index.js',<% } %>
-        './testem.js',
-        './blueprints/*/index.js',
-        './config/**/*.js',<% if (blueprint === 'app') { %>
-        './lib/*/index.js',
-        './server/**/*.js',<% } else { %>
-        './tests/dummy/config/**/*.js',<% } %>
-      ],
-<% if (!typescript) { %>      parserOptions: {
-        sourceType: 'script',
-      },
-<% } %>      env: {
-        browser: false,
-        node: true,
-      },
-      extends: ['plugin:n/recommended'],
-    },
-    {
-      // test files
-      files: ['tests/**/*-test.{js,ts}'],
-      extends: ['plugin:qunit/recommended'],
-    },
-  ],
-};
diff --git a/blueprints/app/files/.prettierrc.js b/blueprints/app/files/.prettierrc.js
index e5f7b6d1e..8d067814c 100644
--- a/blueprints/app/files/.prettierrc.js
+++ b/blueprints/app/files/.prettierrc.js
@@ -1,12 +1,20 @@
 'use strict';
 
 module.exports = {
+  plugins: ['prettier-plugin-ember-template-tag'],
   overrides: [
     {
-      files: '*.{js,ts}',
+      files: '*.{js,ts,mjs,mts,cjs,cts}',
       options: {
         singleQuote: true,
       },
     },
+    {
+      files: '*.{gjs,gts}',
+      options: {
+        singleQuote: true,
+        templateSingleQuote: false,
+      },
+    },
   ],
 };
diff --git a/blueprints/app/files/_js_eslint.config.mjs b/blueprints/app/files/_js_eslint.config.mjs
new file mode 100644
index 000000000..e05042fd2
--- /dev/null
+++ b/blueprints/app/files/_js_eslint.config.mjs
@@ -0,0 +1,119 @@
+import globals from 'globals';
+import js from '@eslint/js';
+
+import ember from 'eslint-plugin-ember';
+import emberRecommended from 'eslint-plugin-ember/configs/recommended';
+import gjsRecommended from 'eslint-plugin-ember/configs/recommended-gjs';
+
+import prettier from 'eslint-plugin-prettier/recommended';
+import qunit from 'eslint-plugin-qunit';
+import n from 'eslint-plugin-n';
+
+import emberParser from 'ember-eslint-parser';
+import babelParser from '@babel/eslint-parser';
+
+const esmParserOptions = {
+  ecmaFeatures: { modules: true },
+  ecmaVersion: 'latest',
+  requireConfigFile: false,
+  babelOptions: {
+    plugins: [
+      ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }],
+    ],
+  },
+};
+
+export default [
+  js.configs.recommended,
+  prettier,
+  {
+    ignores: ['dist/', 'node_modules/', 'coverage/', '!**/.*'],
+    linterOptions: {
+      reportUnusedDisableDirectives: 'error',
+    },
+  },
+  {
+    files: ['**/*.js'],
+    languageOptions: {
+      parser: babelParser,
+      parserOptions: esmParserOptions,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+      ...gjsRecommended.rules,
+    },
+  },
+  {
+    files: ['**/*.gjs'],
+    languageOptions: {
+      parser: emberParser,
+      parserOptions: esmParserOptions,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+      ...gjsRecommended.rules,
+    },
+  },
+  {
+    files: ['tests/**/*-test.{js,gjs}'],
+    plugins: {
+      qunit,
+    },
+  },
+  /**
+   * CJS node files
+   */
+  {
+    files: [
+      '**/*.cjs',
+      'config/**/*.js',
+      'testem.js',
+      'testem*.js',
+      '.prettierrc.js',
+      '.stylelintrc.js',
+      '.template-lintrc.js',
+      'ember-cli-build.js',
+    ],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'script',
+      ecmaVersion: 'latest',
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+  /**
+   * ESM node files
+   */
+  {
+    files: ['*.mjs'],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'module',
+      ecmaVersion: 'latest',
+      parserOptions: esmParserOptions,
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+];
diff --git a/blueprints/app/files/_ts_eslint.config.mjs b/blueprints/app/files/_ts_eslint.config.mjs
new file mode 100644
index 000000000..8acc12d10
--- /dev/null
+++ b/blueprints/app/files/_ts_eslint.config.mjs
@@ -0,0 +1,155 @@
+import globals from 'globals';
+import js from '@eslint/js';
+
+import ts from 'typescript-eslint';
+
+import ember from 'eslint-plugin-ember';
+import emberRecommended from 'eslint-plugin-ember/configs/recommended';
+import gjsRecommended from 'eslint-plugin-ember/configs/recommended-gjs';
+import gtsRecommended from 'eslint-plugin-ember/configs/recommended-gts';
+
+import prettier from 'eslint-plugin-prettier/recommended';
+import qunit from 'eslint-plugin-qunit';
+import n from 'eslint-plugin-n';
+
+import emberParser from 'ember-eslint-parser';
+import babelParser from '@babel/eslint-parser';
+
+const parserOptions = {
+  esm: {
+    js: {
+      ecmaFeatures: { modules: true },
+      ecmaVersion: 'latest',
+      requireConfigFile: false,
+      babelOptions: {
+        plugins: [
+          [
+            '@babel/plugin-proposal-decorators',
+            { decoratorsBeforeExport: true },
+          ],
+        ],
+      },
+    },
+    ts: {
+      projectService: true,
+      tsconfigRootDir: import.meta.dirname,
+    },
+  },
+};
+
+export default ts.config(
+  js.configs.recommended,
+  prettier,
+  {
+    files: ['**/*.js'],
+    languageOptions: {
+      parser: babelParser,
+      parserOptions: parserOptions.esm.js,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+    },
+  },
+  {
+    files: ['**/*.ts'],
+    plugins: { ember },
+    languageOptions: {
+      parserOptions: parserOptions.esm.ts,
+    },
+    extends: [...ts.configs.strictTypeChecked, ...emberRecommended],
+  },
+  {
+    files: ['**/*.gjs'],
+    languageOptions: {
+      parser: emberParser,
+      parserOptions: parserOptions.esm.js,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+      ...gjsRecommended.rules,
+    },
+  },
+  {
+    files: ['**/*.gts'],
+    plugins: { ember },
+    languageOptions: {
+      parserOptions: parserOptions.esm.ts,
+    },
+    extends: [
+      ...ts.configs.strictTypeChecked,
+      ...emberRecommended,
+      ...gtsRecommended,
+    ],
+  },
+  {
+    files: ['tests/**/*-test.{js,gjs}'],
+    plugins: {
+      qunit,
+    },
+  },
+  /**
+   * CJS node files
+   */
+  {
+    files: [
+      '**/*.cjs',
+      'config/**/*.js',
+      'testem.js',
+      'testem*.js',
+      '.prettierrc.js',
+      '.stylelintrc.js',
+      '.template-lintrc.js',
+      'ember-cli-build.js',
+    ],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'script',
+      ecmaVersion: 'latest',
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+  /**
+   * ESM node files
+   */
+  {
+    files: ['*.mjs'],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'module',
+      ecmaVersion: 'latest',
+      parserOptions: parserOptions.esm.js,
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+  /**
+   * Settings
+   */
+  {
+    ignores: ['dist/', 'node_modules/', 'coverage/', '!**/.*'],
+    linterOptions: {
+      reportUnusedDisableDirectives: 'error',
+    },
+  }
+);
diff --git a/blueprints/app/files/package.json b/blueprints/app/files/package.json
index 66442a3c8..3d7614cf5 100644
--- a/blueprints/app/files/package.json
+++ b/blueprints/app/files/package.json
@@ -45,15 +45,17 @@
     "@embroider/compat": "^3.6.1",
     "@embroider/core": "^3.4.16",
     "@embroider/webpack": "^4.0.5<% } %>",
+    "@eslint/js": "^9.12.0",
     "@glimmer/component": "^1.1.2",
     "@glimmer/tracking": "^1.1.2<% if (typescript) { %>",
     "@glint/environment-ember-loose": "^1.4.0",
     "@glint/template": "^1.4.0",
     "@tsconfig/ember": "^3.0.8",
+    "@types/eslint__js": "^8.42.3",
     "@types/qunit": "^2.19.10",
     "@types/rsvp": "^4.0.9",
-    "@typescript-eslint/eslint-plugin": "^7.18.0",
-    "@typescript-eslint/parser": "^7.18.0",
+    "@typescript-eslint/eslint-plugin": "^8.8.1",
+    "@typescript-eslint/parser": "^8.8.1",
     "@warp-drive/core-types": "~0.0.0-beta.11<% } %>",
     "broccoli-asset-rev": "^3.0.0",
     "concurrently": "^9.0.1",
@@ -77,21 +79,24 @@
     "ember-source": "~6.0.0-beta.1",
     "ember-template-lint": "^6.0.0<% if (welcome) { %>",
     "ember-welcome-page": "^7.0.2<% } %>",
-    "eslint": "^8.57.1",
+    "eslint": "^9.12.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-ember": "^12.2.1",
-    "eslint-plugin-n": "^16.6.2",
+    "eslint-plugin-n": "^17.11.1",
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-qunit": "^8.1.2",
+    "globals": "^15.11.0",
     "loader.js": "^4.7.0",
     "prettier": "^3.3.3",
+    "prettier-plugin-ember-template-tag": "^2.0.2",
     "qunit": "^2.22.0",
     "qunit-dom": "^3.2.1",
     "stylelint": "^16.10.0",
     "stylelint-config-standard": "^36.0.1",
     "stylelint-prettier": "^5.0.2",
     "tracked-built-ins": "^3.3.0<% if (typescript) { %>",
-    "typescript": "^5.6.2<% } %>",
+    "typescript": "^5.6.2",
+    "typescript-eslint": "^8.8.1<% } %>",
     "webpack": "^5.95.0"
   },
   "engines": {
diff --git a/blueprints/app/files/tsconfig.json b/blueprints/app/files/tsconfig.json
index 7bd686de2..9387ba942 100644
--- a/blueprints/app/files/tsconfig.json
+++ b/blueprints/app/files/tsconfig.json
@@ -11,7 +11,7 @@
       "*": ["types/*"]
     },
     "types": [
-      "ember-source/types",
+      "./node_modules/ember-source/types/stable",
       "./node_modules/ember-data/unstable-preview-types",
       "./node_modules/@ember-data/store/unstable-preview-types",
       "./node_modules/@ember-data/adapter/unstable-preview-types",
diff --git a/blueprints/app/index.js b/blueprints/app/index.js
index 6b823c9c6..bb91e575b 100644
--- a/blueprints/app/index.js
+++ b/blueprints/app/index.js
@@ -115,4 +115,47 @@ module.exports = {
     this.ui.writeLine('');
     this.ui.writeLine(prependEmoji('✨', `Creating a new Ember app in ${chalk.yellow(process.cwd())}:`));
   },
+
+  /**
+   * @override
+   *
+   * This modification of buildFileInfo allows our differing
+   * input files to output to a single file, depending on the options.
+   * For example:
+   *
+   *   for javascript,
+   *     _ts_eslint.config.mjs is deleted
+   *     _js_eslint.config.mjs is renamed to eslint.config.mjs
+   *
+   *   for typescript,
+   *     _js_eslint.config.mjs is deleted
+   *     _ts_eslint.config.mjs is renamed to eslint.config.mjs
+   */
+  buildFileInfo(intoDir, templateVariables, file, options) {
+    let fileInfo = this._super.buildFileInfo.apply(this, arguments);
+
+    if (file.includes('_js_')) {
+      if (options.typescript) {
+        return null;
+      }
+
+      fileInfo.outputBasePath = fileInfo.outputPath.replace('_js_', '');
+      fileInfo.outputPath = fileInfo.outputPath.replace('_js_', '');
+      fileInfo.displayPath = fileInfo.outputPath.replace('_js_', '');
+      return fileInfo;
+    }
+
+    if (file.includes('_ts_')) {
+      if (!options.typescript) {
+        return null;
+      }
+
+      fileInfo.outputBasePath = fileInfo.outputPath.replace('_ts_', '');
+      fileInfo.outputPath = fileInfo.outputPath.replace('_ts_', '');
+      fileInfo.displayPath = fileInfo.outputPath.replace('_ts_', '');
+      return fileInfo;
+    }
+
+    return fileInfo;
+  },
 };
diff --git a/lib/models/blueprint.js b/lib/models/blueprint.js
index c27674243..bb1b1caf9 100644
--- a/lib/models/blueprint.js
+++ b/lib/models/blueprint.js
@@ -457,7 +457,7 @@ let Blueprint = CoreObject.extend({
     await beforeHook.call(this, options, locals);
 
     // gather fileInfos to be processed
-    let fileInfos = await process.call(this, intoDir, locals);
+    let fileInfos = await process.call(this, intoDir, locals, options);
 
     // commit changes for each FileInfo (with prompting as needed)
     await Promise.all(fileInfos.map((info) => this._commit(info)));
@@ -781,9 +781,10 @@ let Blueprint = CoreObject.extend({
     @param {Function} destPath
     @param {Object} templateVariables
     @param {String} file
-    @return {FileInfo}
+    @param {Object} options may be used when buildFileInfo is customized in a blueprint
+    @return {FileInfo | null}
   */
-  buildFileInfo(intoDir, templateVariables, file) {
+  buildFileInfo(intoDir, templateVariables, file /*, options */) {
     let mappedPath = this.mapFile(file, templateVariables);
 
     return new FileInfo({
@@ -813,10 +814,13 @@ let Blueprint = CoreObject.extend({
     @param {Array} files
     @param {String} intoDir
     @param {Object} templateVariables
+    @param {Object} options
     @return {Array} file infos
   */
-  _getFileInfos(files, intoDir, templateVariables) {
-    return files.map(this.buildFileInfo.bind(this, intoDir, templateVariables));
+  _getFileInfos(files, intoDir, templateVariables, options) {
+    return files
+      .map((file) => this.buildFileInfo.call(this, intoDir, templateVariables, file, options))
+      .filter(Boolean);
   },
 
   /**
@@ -863,11 +867,12 @@ let Blueprint = CoreObject.extend({
     @method processFiles
     @param {String} intoDir
     @param {Object} templateVariables
+    @param {Object} options
     @return {Promise<FileInfo[]>}
   */
-  processFiles(intoDir, templateVariables) {
+  processFiles(intoDir, templateVariables, options) {
     let files = this._getFilesForInstall(templateVariables.targetFiles);
-    let fileInfos = this._getFileInfos(files, intoDir, templateVariables);
+    let fileInfos = this._getFileInfos(files, intoDir, templateVariables, options);
     this._checkForNoMatch(fileInfos, templateVariables.rawArgs);
 
     this._ignoreUpdateFiles();


Test patch:
diff --git a/tests/acceptance/init-test.js b/tests/acceptance/init-test.js
index 5b8a1ace7..c34aadd4f 100644
--- a/tests/acceptance/init-test.js
+++ b/tests/acceptance/init-test.js
@@ -50,9 +50,14 @@ describe('Acceptance: ember init', function () {
     // ignore .travis.yml and TypeScript files
     let expected = walkSync(blueprintPath, {
       ignore: ['.travis.yml', 'tsconfig.json', 'types', 'app/config'],
-    })
-      .map((name) => (typescript ? name : name.replace(/\.ts$/, '.js')))
-      .sort();
+    }).map((name) => (typescript ? name : name.replace(/\.ts$/, '.js')));
+
+    // This style of assertion can't handle conditionally available files
+    if (expected.some((x) => x.endsWith('eslint.config.mjs'))) {
+      expected = [...expected.filter((x) => !x.endsWith('eslint.config.mjs')), 'eslint.config.mjs'];
+    }
+    expected.sort();
+
     let actual = walkSync('.').sort();
 
     Object.keys(Blueprint.renamedFiles).forEach((srcFile) => {
diff --git a/tests/acceptance/new-test.js b/tests/acceptance/new-test.js
index a00974dee..2f722e0ae 100644
--- a/tests/acceptance/new-test.js
+++ b/tests/acceptance/new-test.js
@@ -46,6 +46,11 @@ describe('Acceptance: ember new', function () {
       ignore: ['.travis.yml', 'tsconfig.json', 'types', 'app/config'],
     }).map((name) => (typescript ? name : name.replace(/\.ts$/, '.js')));
 
+    // This style of assertion can't handle conditionally available files
+    if (expected.some((x) => x.endsWith('eslint.config.mjs'))) {
+      expected = [...expected.filter((x) => !x.endsWith('eslint.config.mjs')), 'eslint.config.mjs'];
+    }
+
     let actual = walkSync('.').sort();
     let directory = path.basename(process.cwd());
 
@@ -425,7 +430,9 @@ describe('Acceptance: ember new', function () {
 
   describe('verify fixtures', function () {
     function checkEslintConfig(fixturePath) {
-      expect(file('.eslintrc.js')).to.equal(file(path.join(__dirname, '../fixtures', fixturePath, '.eslintrc.js')));
+      expect(file('eslint.config.mjs')).to.equal(
+        file(path.join(__dirname, '../fixtures', fixturePath, 'eslint.config.mjs'))
+      );
     }
 
     function checkFileWithEmberCLIVersionReplacement(fixtureName, fileName) {
diff --git a/tests/fixtures/addon/.eslintrc.js b/tests/fixtures/addon/.eslintrc.js
deleted file mode 100644
index ade61fb01..000000000
--- a/tests/fixtures/addon/.eslintrc.js
+++ /dev/null
@@ -1,56 +0,0 @@
-'use strict';
-
-module.exports = {
-  root: true,
-  parser: '@babel/eslint-parser',
-  parserOptions: {
-    ecmaVersion: 'latest',
-    sourceType: 'module',
-    requireConfigFile: false,
-    babelOptions: {
-      plugins: [
-        ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }],
-      ],
-    },
-  },
-  plugins: ['ember'],
-  extends: [
-    'eslint:recommended',
-    'plugin:ember/recommended',
-    'plugin:prettier/recommended',
-  ],
-  env: {
-    browser: true,
-  },
-  rules: {},
-  overrides: [
-    // node files
-    {
-      files: [
-        './.eslintrc.js',
-        './.prettierrc.js',
-        './.stylelintrc.js',
-        './.template-lintrc.js',
-        './ember-cli-build.js',
-        './index.js',
-        './testem.js',
-        './blueprints/*/index.js',
-        './config/**/*.js',
-        './tests/dummy/config/**/*.js',
-      ],
-      parserOptions: {
-        sourceType: 'script',
-      },
-      env: {
-        browser: false,
-        node: true,
-      },
-      extends: ['plugin:n/recommended'],
-    },
-    {
-      // test files
-      files: ['tests/**/*-test.{js,ts}'],
-      extends: ['plugin:qunit/recommended'],
-    },
-  ],
-};
diff --git a/tests/fixtures/addon/defaults/package.json b/tests/fixtures/addon/defaults/package.json
index fe8b3df0b..284e32989 100644
--- a/tests/fixtures/addon/defaults/package.json
+++ b/tests/fixtures/addon/defaults/package.json
@@ -38,6 +38,7 @@
     "@ember/optional-features": "^2.1.0",
     "@ember/test-helpers": "^4.0.4",
     "@embroider/test-setup": "^4.0.0",
+    "@eslint/js": "^9.12.0",
     "@glimmer/component": "^1.1.2",
     "@glimmer/tracking": "^1.1.2",
     "broccoli-asset-rev": "^3.0.0",
@@ -57,14 +58,16 @@
     "ember-source-channel-url": "^3.0.0",
     "ember-template-lint": "^6.0.0",
     "ember-try": "^3.0.0",
-    "eslint": "^8.57.1",
+    "eslint": "^9.12.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-ember": "^12.2.1",
-    "eslint-plugin-n": "^16.6.2",
+    "eslint-plugin-n": "^17.11.1",
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-qunit": "^8.1.2",
+    "globals": "^15.11.0",
     "loader.js": "^4.7.0",
     "prettier": "^3.3.3",
+    "prettier-plugin-ember-template-tag": "^2.0.2",
     "qunit": "^2.22.0",
     "qunit-dom": "^3.2.1",
     "stylelint": "^16.10.0",
diff --git a/tests/fixtures/addon/eslint.config.mjs b/tests/fixtures/addon/eslint.config.mjs
new file mode 100644
index 000000000..e05042fd2
--- /dev/null
+++ b/tests/fixtures/addon/eslint.config.mjs
@@ -0,0 +1,119 @@
+import globals from 'globals';
+import js from '@eslint/js';
+
+import ember from 'eslint-plugin-ember';
+import emberRecommended from 'eslint-plugin-ember/configs/recommended';
+import gjsRecommended from 'eslint-plugin-ember/configs/recommended-gjs';
+
+import prettier from 'eslint-plugin-prettier/recommended';
+import qunit from 'eslint-plugin-qunit';
+import n from 'eslint-plugin-n';
+
+import emberParser from 'ember-eslint-parser';
+import babelParser from '@babel/eslint-parser';
+
+const esmParserOptions = {
+  ecmaFeatures: { modules: true },
+  ecmaVersion: 'latest',
+  requireConfigFile: false,
+  babelOptions: {
+    plugins: [
+      ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }],
+    ],
+  },
+};
+
+export default [
+  js.configs.recommended,
+  prettier,
+  {
+    ignores: ['dist/', 'node_modules/', 'coverage/', '!**/.*'],
+    linterOptions: {
+      reportUnusedDisableDirectives: 'error',
+    },
+  },
+  {
+    files: ['**/*.js'],
+    languageOptions: {
+      parser: babelParser,
+      parserOptions: esmParserOptions,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+      ...gjsRecommended.rules,
+    },
+  },
+  {
+    files: ['**/*.gjs'],
+    languageOptions: {
+      parser: emberParser,
+      parserOptions: esmParserOptions,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+      ...gjsRecommended.rules,
+    },
+  },
+  {
+    files: ['tests/**/*-test.{js,gjs}'],
+    plugins: {
+      qunit,
+    },
+  },
+  /**
+   * CJS node files
+   */
+  {
+    files: [
+      '**/*.cjs',
+      'config/**/*.js',
+      'testem.js',
+      'testem*.js',
+      '.prettierrc.js',
+      '.stylelintrc.js',
+      '.template-lintrc.js',
+      'ember-cli-build.js',
+    ],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'script',
+      ecmaVersion: 'latest',
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+  /**
+   * ESM node files
+   */
+  {
+    files: ['*.mjs'],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'module',
+      ecmaVersion: 'latest',
+      parserOptions: esmParserOptions,
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+];
diff --git a/tests/fixtures/addon/pnpm/package.json b/tests/fixtures/addon/pnpm/package.json
index be3689e80..bcbe66543 100644
--- a/tests/fixtures/addon/pnpm/package.json
+++ b/tests/fixtures/addon/pnpm/package.json
@@ -38,6 +38,7 @@
     "@ember/optional-features": "^2.1.0",
     "@ember/test-helpers": "^4.0.4",
     "@embroider/test-setup": "^4.0.0",
+    "@eslint/js": "^9.12.0",
     "@glimmer/component": "^1.1.2",
     "@glimmer/tracking": "^1.1.2",
     "broccoli-asset-rev": "^3.0.0",
@@ -58,14 +59,16 @@
     "ember-template-lint": "^6.0.0",
     "ember-try": "^3.0.0",
     "ember-welcome-page": "^7.0.2",
-    "eslint": "^8.57.1",
+    "eslint": "^9.12.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-ember": "^12.2.1",
-    "eslint-plugin-n": "^16.6.2",
+    "eslint-plugin-n": "^17.11.1",
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-qunit": "^8.1.2",
+    "globals": "^15.11.0",
     "loader.js": "^4.7.0",
     "prettier": "^3.3.3",
+    "prettier-plugin-ember-template-tag": "^2.0.2",
     "qunit": "^2.22.0",
     "qunit-dom": "^3.2.1",
     "stylelint": "^16.10.0",
diff --git a/tests/fixtures/addon/typescript/.eslintrc.js b/tests/fixtures/addon/typescript/.eslintrc.js
deleted file mode 100644
index 71f4e8aa9..000000000
--- a/tests/fixtures/addon/typescript/.eslintrc.js
+++ /dev/null
@@ -1,55 +0,0 @@
-'use strict';
-
-module.exports = {
-  root: true,
-  parser: '@typescript-eslint/parser',
-  parserOptions: {
-    ecmaVersion: 'latest',
-  },
-  plugins: ['ember', '@typescript-eslint'],
-  extends: [
-    'eslint:recommended',
-    'plugin:ember/recommended',
-    'plugin:prettier/recommended',
-  ],
-  env: {
-    browser: true,
-  },
-  rules: {},
-  overrides: [
-    // ts files
-    {
-      files: ['**/*.ts'],
-      extends: [
-        'plugin:@typescript-eslint/eslint-recommended',
-        'plugin:@typescript-eslint/recommended',
-      ],
-      rules: {},
-    },
-    // node files
-    {
-      files: [
-        './.eslintrc.js',
-        './.prettierrc.js',
-        './.stylelintrc.js',
-        './.template-lintrc.js',
-        './ember-cli-build.js',
-        './index.js',
-        './testem.js',
-        './blueprints/*/index.js',
-        './config/**/*.js',
-        './tests/dummy/config/**/*.js',
-      ],
-      env: {
-        browser: false,
-        node: true,
-      },
-      extends: ['plugin:n/recommended'],
-    },
-    {
-      // test files
-      files: ['tests/**/*-test.{js,ts}'],
-      extends: ['plugin:qunit/recommended'],
-    },
-  ],
-};
diff --git a/tests/fixtures/addon/typescript/eslint.config.mjs b/tests/fixtures/addon/typescript/eslint.config.mjs
new file mode 100644
index 000000000..8acc12d10
--- /dev/null
+++ b/tests/fixtures/addon/typescript/eslint.config.mjs
@@ -0,0 +1,155 @@
+import globals from 'globals';
+import js from '@eslint/js';
+
+import ts from 'typescript-eslint';
+
+import ember from 'eslint-plugin-ember';
+import emberRecommended from 'eslint-plugin-ember/configs/recommended';
+import gjsRecommended from 'eslint-plugin-ember/configs/recommended-gjs';
+import gtsRecommended from 'eslint-plugin-ember/configs/recommended-gts';
+
+import prettier from 'eslint-plugin-prettier/recommended';
+import qunit from 'eslint-plugin-qunit';
+import n from 'eslint-plugin-n';
+
+import emberParser from 'ember-eslint-parser';
+import babelParser from '@babel/eslint-parser';
+
+const parserOptions = {
+  esm: {
+    js: {
+      ecmaFeatures: { modules: true },
+      ecmaVersion: 'latest',
+      requireConfigFile: false,
+      babelOptions: {
+        plugins: [
+          [
+            '@babel/plugin-proposal-decorators',
+            { decoratorsBeforeExport: true },
+          ],
+        ],
+      },
+    },
+    ts: {
+      projectService: true,
+      tsconfigRootDir: import.meta.dirname,
+    },
+  },
+};
+
+export default ts.config(
+  js.configs.recommended,
+  prettier,
+  {
+    files: ['**/*.js'],
+    languageOptions: {
+      parser: babelParser,
+      parserOptions: parserOptions.esm.js,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+    },
+  },
+  {
+    files: ['**/*.ts'],
+    plugins: { ember },
+    languageOptions: {
+      parserOptions: parserOptions.esm.ts,
+    },
+    extends: [...ts.configs.strictTypeChecked, ...emberRecommended],
+  },
+  {
+    files: ['**/*.gjs'],
+    languageOptions: {
+      parser: emberParser,
+      parserOptions: parserOptions.esm.js,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+      ...gjsRecommended.rules,
+    },
+  },
+  {
+    files: ['**/*.gts'],
+    plugins: { ember },
+    languageOptions: {
+      parserOptions: parserOptions.esm.ts,
+    },
+    extends: [
+      ...ts.configs.strictTypeChecked,
+      ...emberRecommended,
+      ...gtsRecommended,
+    ],
+  },
+  {
+    files: ['tests/**/*-test.{js,gjs}'],
+    plugins: {
+      qunit,
+    },
+  },
+  /**
+   * CJS node files
+   */
+  {
+    files: [
+      '**/*.cjs',
+      'config/**/*.js',
+      'testem.js',
+      'testem*.js',
+      '.prettierrc.js',
+      '.stylelintrc.js',
+      '.template-lintrc.js',
+      'ember-cli-build.js',
+    ],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'script',
+      ecmaVersion: 'latest',
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+  /**
+   * ESM node files
+   */
+  {
+    files: ['*.mjs'],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'module',
+      ecmaVersion: 'latest',
+      parserOptions: parserOptions.esm.js,
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+  /**
+   * Settings
+   */
+  {
+    ignores: ['dist/', 'node_modules/', 'coverage/', '!**/.*'],
+    linterOptions: {
+      reportUnusedDisableDirectives: 'error',
+    },
+  }
+);
diff --git a/tests/fixtures/addon/typescript/package.json b/tests/fixtures/addon/typescript/package.json
index 4e582838b..ce7277993 100644
--- a/tests/fixtures/addon/typescript/package.json
+++ b/tests/fixtures/addon/typescript/package.json
@@ -54,15 +54,17 @@
     "@ember/optional-features": "^2.1.0",
     "@ember/test-helpers": "^4.0.4",
     "@embroider/test-setup": "^4.0.0",
+    "@eslint/js": "^9.12.0",
     "@glimmer/component": "^1.1.2",
     "@glimmer/tracking": "^1.1.2",
     "@glint/environment-ember-loose": "^1.4.0",
     "@glint/template": "^1.4.0",
     "@tsconfig/ember": "^3.0.8",
+    "@types/eslint__js": "^8.42.3",
     "@types/qunit": "^2.19.10",
     "@types/rsvp": "^4.0.9",
-    "@typescript-eslint/eslint-plugin": "^7.18.0",
-    "@typescript-eslint/parser": "^7.18.0",
+    "@typescript-eslint/eslint-plugin": "^8.8.1",
+    "@typescript-eslint/parser": "^8.8.1",
     "broccoli-asset-rev": "^3.0.0",
     "concurrently": "^9.0.1",
     "ember-auto-import": "^2.8.1",
@@ -80,14 +82,16 @@
     "ember-source-channel-url": "^3.0.0",
     "ember-template-lint": "^6.0.0",
     "ember-try": "^3.0.0",
-    "eslint": "^8.57.1",
+    "eslint": "^9.12.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-ember": "^12.2.1",
-    "eslint-plugin-n": "^16.6.2",
+    "eslint-plugin-n": "^17.11.1",
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-qunit": "^8.1.2",
+    "globals": "^15.11.0",
     "loader.js": "^4.7.0",
     "prettier": "^3.3.3",
+    "prettier-plugin-ember-template-tag": "^2.0.2",
     "qunit": "^2.22.0",
     "qunit-dom": "^3.2.1",
     "rimraf": "^5.0.1",
@@ -95,6 +99,7 @@
     "stylelint-config-standard": "^36.0.1",
     "stylelint-prettier": "^5.0.2",
     "typescript": "^5.6.2",
+    "typescript-eslint": "^8.8.1",
     "webpack": "^5.95.0"
   },
   "peerDependencies": {
diff --git a/tests/fixtures/addon/yarn/package.json b/tests/fixtures/addon/yarn/package.json
index 8e24a87b1..2c49c1f48 100644
--- a/tests/fixtures/addon/yarn/package.json
+++ b/tests/fixtures/addon/yarn/package.json
@@ -38,6 +38,7 @@
     "@ember/optional-features": "^2.1.0",
     "@ember/test-helpers": "^4.0.4",
     "@embroider/test-setup": "^4.0.0",
+    "@eslint/js": "^9.12.0",
     "@glimmer/component": "^1.1.2",
     "@glimmer/tracking": "^1.1.2",
     "broccoli-asset-rev": "^3.0.0",
@@ -58,14 +59,16 @@
     "ember-template-lint": "^6.0.0",
     "ember-try": "^3.0.0",
     "ember-welcome-page": "^7.0.2",
-    "eslint": "^8.57.1",
+    "eslint": "^9.12.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-ember": "^12.2.1",
-    "eslint-plugin-n": "^16.6.2",
+    "eslint-plugin-n": "^17.11.1",
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-qunit": "^8.1.2",
+    "globals": "^15.11.0",
     "loader.js": "^4.7.0",
     "prettier": "^3.3.3",
+    "prettier-plugin-ember-template-tag": "^2.0.2",
     "qunit": "^2.22.0",
     "qunit-dom": "^3.2.1",
     "stylelint": "^16.10.0",
diff --git a/tests/fixtures/app/.eslintrc.js b/tests/fixtures/app/.eslintrc.js
deleted file mode 100644
index 25ceaab40..000000000
--- a/tests/fixtures/app/.eslintrc.js
+++ /dev/null
@@ -1,56 +0,0 @@
-'use strict';
-
-module.exports = {
-  root: true,
-  parser: '@babel/eslint-parser',
-  parserOptions: {
-    ecmaVersion: 'latest',
-    sourceType: 'module',
-    requireConfigFile: false,
-    babelOptions: {
-      plugins: [
-        ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }],
-      ],
-    },
-  },
-  plugins: ['ember'],
-  extends: [
-    'eslint:recommended',
-    'plugin:ember/recommended',
-    'plugin:prettier/recommended',
-  ],
-  env: {
-    browser: true,
-  },
-  rules: {},
-  overrides: [
-    // node files
-    {
-      files: [
-        './.eslintrc.js',
-        './.prettierrc.js',
-        './.stylelintrc.js',
-        './.template-lintrc.js',
-        './ember-cli-build.js',
-        './testem.js',
-        './blueprints/*/index.js',
-        './config/**/*.js',
-        './lib/*/index.js',
-        './server/**/*.js',
-      ],
-      parserOptions: {
-        sourceType: 'script',
-      },
-      env: {
-        browser: false,
-        node: true,
-      },
-      extends: ['plugin:n/recommended'],
-    },
-    {
-      // test files
-      files: ['tests/**/*-test.{js,ts}'],
-      extends: ['plugin:qunit/recommended'],
-    },
-  ],
-};
diff --git a/tests/fixtures/app/defaults/package.json b/tests/fixtures/app/defaults/package.json
index 5b5eb25ef..f66bad4da 100644
--- a/tests/fixtures/app/defaults/package.json
+++ b/tests/fixtures/app/defaults/package.json
@@ -31,6 +31,7 @@
     "@ember/optional-features": "^2.1.0",
     "@ember/string": "^4.0.0",
     "@ember/test-helpers": "^4.0.4",
+    "@eslint/js": "^9.12.0",
     "@glimmer/component": "^1.1.2",
     "@glimmer/tracking": "^1.1.2",
     "broccoli-asset-rev": "^3.0.0",
@@ -55,14 +56,16 @@
     "ember-source": "~6.0.0-beta.1",
     "ember-template-lint": "^6.0.0",
     "ember-welcome-page": "^7.0.2",
-    "eslint": "^8.57.1",
+    "eslint": "^9.12.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-ember": "^12.2.1",
-    "eslint-plugin-n": "^16.6.2",
+    "eslint-plugin-n": "^17.11.1",
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-qunit": "^8.1.2",
+    "globals": "^15.11.0",
     "loader.js": "^4.7.0",
     "prettier": "^3.3.3",
+    "prettier-plugin-ember-template-tag": "^2.0.2",
     "qunit": "^2.22.0",
     "qunit-dom": "^3.2.1",
     "stylelint": "^16.10.0",
diff --git a/tests/fixtures/app/embroider-no-welcome/package.json b/tests/fixtures/app/embroider-no-welcome/package.json
index 2a525b0d9..2977a2108 100644
--- a/tests/fixtures/app/embroider-no-welcome/package.json
+++ b/tests/fixtures/app/embroider-no-welcome/package.json
@@ -34,6 +34,7 @@
     "@embroider/compat": "^3.6.1",
     "@embroider/core": "^3.4.16",
     "@embroider/webpack": "^4.0.5",
+    "@eslint/js": "^9.12.0",
     "@glimmer/component": "^1.1.2",
     "@glimmer/tracking": "^1.1.2",
     "broccoli-asset-rev": "^3.0.0",
@@ -55,14 +56,16 @@
     "ember-resolver": "^13.0.2",
     "ember-source": "~6.0.0-beta.1",
     "ember-template-lint": "^6.0.0",
-    "eslint": "^8.57.1",
+    "eslint": "^9.12.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-ember": "^12.2.1",
-    "eslint-plugin-n": "^16.6.2",
+    "eslint-plugin-n": "^17.11.1",
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-qunit": "^8.1.2",
+    "globals": "^15.11.0",
     "loader.js": "^4.7.0",
     "prettier": "^3.3.3",
+    "prettier-plugin-ember-template-tag": "^2.0.2",
     "qunit": "^2.22.0",
     "qunit-dom": "^3.2.1",
     "stylelint": "^16.10.0",
diff --git a/tests/fixtures/app/embroider-pnpm/package.json b/tests/fixtures/app/embroider-pnpm/package.json
index 02359499c..4e5bb69da 100644
--- a/tests/fixtures/app/embroider-pnpm/package.json
+++ b/tests/fixtures/app/embroider-pnpm/package.json
@@ -34,6 +34,7 @@
     "@embroider/compat": "^3.6.1",
     "@embroider/core": "^3.4.16",
     "@embroider/webpack": "^4.0.5",
+    "@eslint/js": "^9.12.0",
     "@glimmer/component": "^1.1.2",
     "@glimmer/tracking": "^1.1.2",
     "broccoli-asset-rev": "^3.0.0",
@@ -56,14 +57,16 @@
     "ember-source": "~6.0.0-beta.1",
     "ember-template-lint": "^6.0.0",
     "ember-welcome-page": "^7.0.2",
-    "eslint": "^8.57.1",
+    "eslint": "^9.12.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-ember": "^12.2.1",
-    "eslint-plugin-n": "^16.6.2",
+    "eslint-plugin-n": "^17.11.1",
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-qunit": "^8.1.2",
+    "globals": "^15.11.0",
     "loader.js": "^4.7.0",
     "prettier": "^3.3.3",
+    "prettier-plugin-ember-template-tag": "^2.0.2",
     "qunit": "^2.22.0",
     "qunit-dom": "^3.2.1",
     "stylelint": "^16.10.0",
diff --git a/tests/fixtures/app/embroider-yarn/package.json b/tests/fixtures/app/embroider-yarn/package.json
index 066fde36d..ffbe35527 100644
--- a/tests/fixtures/app/embroider-yarn/package.json
+++ b/tests/fixtures/app/embroider-yarn/package.json
@@ -34,6 +34,7 @@
     "@embroider/compat": "^3.6.1",
     "@embroider/core": "^3.4.16",
     "@embroider/webpack": "^4.0.5",
+    "@eslint/js": "^9.12.0",
     "@glimmer/component": "^1.1.2",
     "@glimmer/tracking": "^1.1.2",
     "broccoli-asset-rev": "^3.0.0",
@@ -56,14 +57,16 @@
     "ember-source": "~6.0.0-beta.1",
     "ember-template-lint": "^6.0.0",
     "ember-welcome-page": "^7.0.2",
-    "eslint": "^8.57.1",
+    "eslint": "^9.12.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-ember": "^12.2.1",
-    "eslint-plugin-n": "^16.6.2",
+    "eslint-plugin-n": "^17.11.1",
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-qunit": "^8.1.2",
+    "globals": "^15.11.0",
     "loader.js": "^4.7.0",
     "prettier": "^3.3.3",
+    "prettier-plugin-ember-template-tag": "^2.0.2",
     "qunit": "^2.22.0",
     "qunit-dom": "^3.2.1",
     "stylelint": "^16.10.0",
diff --git a/tests/fixtures/app/embroider/package.json b/tests/fixtures/app/embroider/package.json
index d3ef72e9c..370885c79 100644
--- a/tests/fixtures/app/embroider/package.json
+++ b/tests/fixtures/app/embroider/package.json
@@ -34,6 +34,7 @@
     "@embroider/compat": "^3.6.1",
     "@embroider/core": "^3.4.16",
     "@embroider/webpack": "^4.0.5",
+    "@eslint/js": "^9.12.0",
     "@glimmer/component": "^1.1.2",
     "@glimmer/tracking": "^1.1.2",
     "broccoli-asset-rev": "^3.0.0",
@@ -56,14 +57,16 @@
     "ember-source": "~6.0.0-beta.1",
     "ember-template-lint": "^6.0.0",
     "ember-welcome-page": "^7.0.2",
-    "eslint": "^8.57.1",
+    "eslint": "^9.12.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-ember": "^12.2.1",
-    "eslint-plugin-n": "^16.6.2",
+    "eslint-plugin-n": "^17.11.1",
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-qunit": "^8.1.2",
+    "globals": "^15.11.0",
     "loader.js": "^4.7.0",
     "prettier": "^3.3.3",
+    "prettier-plugin-ember-template-tag": "^2.0.2",
     "qunit": "^2.22.0",
     "qunit-dom": "^3.2.1",
     "stylelint": "^16.10.0",
diff --git a/tests/fixtures/app/eslint.config.mjs b/tests/fixtures/app/eslint.config.mjs
new file mode 100644
index 000000000..e05042fd2
--- /dev/null
+++ b/tests/fixtures/app/eslint.config.mjs
@@ -0,0 +1,119 @@
+import globals from 'globals';
+import js from '@eslint/js';
+
+import ember from 'eslint-plugin-ember';
+import emberRecommended from 'eslint-plugin-ember/configs/recommended';
+import gjsRecommended from 'eslint-plugin-ember/configs/recommended-gjs';
+
+import prettier from 'eslint-plugin-prettier/recommended';
+import qunit from 'eslint-plugin-qunit';
+import n from 'eslint-plugin-n';
+
+import emberParser from 'ember-eslint-parser';
+import babelParser from '@babel/eslint-parser';
+
+const esmParserOptions = {
+  ecmaFeatures: { modules: true },
+  ecmaVersion: 'latest',
+  requireConfigFile: false,
+  babelOptions: {
+    plugins: [
+      ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }],
+    ],
+  },
+};
+
+export default [
+  js.configs.recommended,
+  prettier,
+  {
+    ignores: ['dist/', 'node_modules/', 'coverage/', '!**/.*'],
+    linterOptions: {
+      reportUnusedDisableDirectives: 'error',
+    },
+  },
+  {
+    files: ['**/*.js'],
+    languageOptions: {
+      parser: babelParser,
+      parserOptions: esmParserOptions,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+      ...gjsRecommended.rules,
+    },
+  },
+  {
+    files: ['**/*.gjs'],
+    languageOptions: {
+      parser: emberParser,
+      parserOptions: esmParserOptions,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+      ...gjsRecommended.rules,
+    },
+  },
+  {
+    files: ['tests/**/*-test.{js,gjs}'],
+    plugins: {
+      qunit,
+    },
+  },
+  /**
+   * CJS node files
+   */
+  {
+    files: [
+      '**/*.cjs',
+      'config/**/*.js',
+      'testem.js',
+      'testem*.js',
+      '.prettierrc.js',
+      '.stylelintrc.js',
+      '.template-lintrc.js',
+      'ember-cli-build.js',
+    ],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'script',
+      ecmaVersion: 'latest',
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+  /**
+   * ESM node files
+   */
+  {
+    files: ['*.mjs'],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'module',
+      ecmaVersion: 'latest',
+      parserOptions: esmParserOptions,
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+];
diff --git a/tests/fixtures/app/npm/package.json b/tests/fixtures/app/npm/package.json
index a9f6858b2..0e1a4832e 100644
--- a/tests/fixtures/app/npm/package.json
+++ b/tests/fixtures/app/npm/package.json
@@ -31,6 +31,7 @@
     "@ember/optional-features": "^2.1.0",
     "@ember/string": "^4.0.0",
     "@ember/test-helpers": "^4.0.4",
+    "@eslint/js": "^9.12.0",
     "@glimmer/component": "^1.1.2",
     "@glimmer/tracking": "^1.1.2",
     "broccoli-asset-rev": "^3.0.0",
@@ -54,14 +55,16 @@
     "ember-resolver": "^13.0.2",
     "ember-source": "~6.0.0-beta.1",
     "ember-template-lint": "^6.0.0",
-    "eslint": "^8.57.1",
+    "eslint": "^9.12.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-ember": "^12.2.1",
-    "eslint-plugin-n": "^16.6.2",
+    "eslint-plugin-n": "^17.11.1",
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-qunit": "^8.1.2",
+    "globals": "^15.11.0",
     "loader.js": "^4.7.0",
     "prettier": "^3.3.3",
+    "prettier-plugin-ember-template-tag": "^2.0.2",
     "qunit": "^2.22.0",
     "qunit-dom": "^3.2.1",
     "stylelint": "^16.10.0",
diff --git a/tests/fixtures/app/pnpm/package.json b/tests/fixtures/app/pnpm/package.json
index c42318ecf..597554ea4 100644
--- a/tests/fixtures/app/pnpm/package.json
+++ b/tests/fixtures/app/pnpm/package.json
@@ -31,6 +31,7 @@
     "@ember/optional-features": "^2.1.0",
     "@ember/string": "^4.0.0",
     "@ember/test-helpers": "^4.0.4",
+    "@eslint/js": "^9.12.0",
     "@glimmer/component": "^1.1.2",
     "@glimmer/tracking": "^1.1.2",
     "broccoli-asset-rev": "^3.0.0",
@@ -55,14 +56,16 @@
     "ember-source": "~6.0.0-beta.1",
     "ember-template-lint": "^6.0.0",
     "ember-welcome-page": "^7.0.2",
-    "eslint": "^8.57.1",
+    "eslint": "^9.12.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-ember": "^12.2.1",
-    "eslint-plugin-n": "^16.6.2",
+    "eslint-plugin-n": "^17.11.1",
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-qunit": "^8.1.2",
+    "globals": "^15.11.0",
     "loader.js": "^4.7.0",
     "prettier": "^3.3.3",
+    "prettier-plugin-ember-template-tag": "^2.0.2",
     "qunit": "^2.22.0",
     "qunit-dom": "^3.2.1",
     "stylelint": "^16.10.0",
diff --git a/tests/fixtures/app/typescript-embroider/.eslintrc.js b/tests/fixtures/app/typescript-embroider/.eslintrc.js
deleted file mode 100644
index 121ac75c9..000000000
--- a/tests/fixtures/app/typescript-embroider/.eslintrc.js
+++ /dev/null
@@ -1,55 +0,0 @@
-'use strict';
-
-module.exports = {
-  root: true,
-  parser: '@typescript-eslint/parser',
-  parserOptions: {
-    ecmaVersion: 'latest',
-  },
-  plugins: ['ember', '@typescript-eslint'],
-  extends: [
-    'eslint:recommended',
-    'plugin:ember/recommended',
-    'plugin:prettier/recommended',
-  ],
-  env: {
-    browser: true,
-  },
-  rules: {},
-  overrides: [
-    // ts files
-    {
-      files: ['**/*.ts'],
-      extends: [
-        'plugin:@typescript-eslint/eslint-recommended',
-        'plugin:@typescript-eslint/recommended',
-      ],
-      rules: {},
-    },
-    // node files
-    {
-      files: [
-        './.eslintrc.js',
-        './.prettierrc.js',
-        './.stylelintrc.js',
-        './.template-lintrc.js',
-        './ember-cli-build.js',
-        './testem.js',
-        './blueprints/*/index.js',
-        './config/**/*.js',
-        './lib/*/index.js',
-        './server/**/*.js',
-      ],
-      env: {
-        browser: false,
-        node: true,
-      },
-      extends: ['plugin:n/recommended'],
-    },
-    {
-      // test files
-      files: ['tests/**/*-test.{js,ts}'],
-      extends: ['plugin:qunit/recommended'],
-    },
-  ],
-};
diff --git a/tests/fixtures/app/typescript-embroider/eslint.config.mjs b/tests/fixtures/app/typescript-embroider/eslint.config.mjs
new file mode 100644
index 000000000..8acc12d10
--- /dev/null
+++ b/tests/fixtures/app/typescript-embroider/eslint.config.mjs
@@ -0,0 +1,155 @@
+import globals from 'globals';
+import js from '@eslint/js';
+
+import ts from 'typescript-eslint';
+
+import ember from 'eslint-plugin-ember';
+import emberRecommended from 'eslint-plugin-ember/configs/recommended';
+import gjsRecommended from 'eslint-plugin-ember/configs/recommended-gjs';
+import gtsRecommended from 'eslint-plugin-ember/configs/recommended-gts';
+
+import prettier from 'eslint-plugin-prettier/recommended';
+import qunit from 'eslint-plugin-qunit';
+import n from 'eslint-plugin-n';
+
+import emberParser from 'ember-eslint-parser';
+import babelParser from '@babel/eslint-parser';
+
+const parserOptions = {
+  esm: {
+    js: {
+      ecmaFeatures: { modules: true },
+      ecmaVersion: 'latest',
+      requireConfigFile: false,
+      babelOptions: {
+        plugins: [
+          [
+            '@babel/plugin-proposal-decorators',
+            { decoratorsBeforeExport: true },
+          ],
+        ],
+      },
+    },
+    ts: {
+      projectService: true,
+      tsconfigRootDir: import.meta.dirname,
+    },
+  },
+};
+
+export default ts.config(
+  js.configs.recommended,
+  prettier,
+  {
+    files: ['**/*.js'],
+    languageOptions: {
+      parser: babelParser,
+      parserOptions: parserOptions.esm.js,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+    },
+  },
+  {
+    files: ['**/*.ts'],
+    plugins: { ember },
+    languageOptions: {
+      parserOptions: parserOptions.esm.ts,
+    },
+    extends: [...ts.configs.strictTypeChecked, ...emberRecommended],
+  },
+  {
+    files: ['**/*.gjs'],
+    languageOptions: {
+      parser: emberParser,
+      parserOptions: parserOptions.esm.js,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+      ...gjsRecommended.rules,
+    },
+  },
+  {
+    files: ['**/*.gts'],
+    plugins: { ember },
+    languageOptions: {
+      parserOptions: parserOptions.esm.ts,
+    },
+    extends: [
+      ...ts.configs.strictTypeChecked,
+      ...emberRecommended,
+      ...gtsRecommended,
+    ],
+  },
+  {
+    files: ['tests/**/*-test.{js,gjs}'],
+    plugins: {
+      qunit,
+    },
+  },
+  /**
+   * CJS node files
+   */
+  {
+    files: [
+      '**/*.cjs',
+      'config/**/*.js',
+      'testem.js',
+      'testem*.js',
+      '.prettierrc.js',
+      '.stylelintrc.js',
+      '.template-lintrc.js',
+      'ember-cli-build.js',
+    ],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'script',
+      ecmaVersion: 'latest',
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+  /**
+   * ESM node files
+   */
+  {
+    files: ['*.mjs'],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'module',
+      ecmaVersion: 'latest',
+      parserOptions: parserOptions.esm.js,
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+  /**
+   * Settings
+   */
+  {
+    ignores: ['dist/', 'node_modules/', 'coverage/', '!**/.*'],
+    linterOptions: {
+      reportUnusedDisableDirectives: 'error',
+    },
+  }
+);
diff --git a/tests/fixtures/app/typescript-embroider/package.json b/tests/fixtures/app/typescript-embroider/package.json
index 78475091f..e22c55c27 100644
--- a/tests/fixtures/app/typescript-embroider/package.json
+++ b/tests/fixtures/app/typescript-embroider/package.json
@@ -43,15 +43,17 @@
     "@embroider/compat": "^3.6.1",
     "@embroider/core": "^3.4.16",
     "@embroider/webpack": "^4.0.5",
+    "@eslint/js": "^9.12.0",
     "@glimmer/component": "^1.1.2",
     "@glimmer/tracking": "^1.1.2",
     "@glint/environment-ember-loose": "^1.4.0",
     "@glint/template": "^1.4.0",
     "@tsconfig/ember": "^3.0.8",
+    "@types/eslint__js": "^8.42.3",
     "@types/qunit": "^2.19.10",
     "@types/rsvp": "^4.0.9",
-    "@typescript-eslint/eslint-plugin": "^7.18.0",
-    "@typescript-eslint/parser": "^7.18.0",
+    "@typescript-eslint/eslint-plugin": "^8.8.1",
+    "@typescript-eslint/parser": "^8.8.1",
     "@warp-drive/core-types": "~0.0.0-beta.11",
     "broccoli-asset-rev": "^3.0.0",
     "concurrently": "^9.0.1",
@@ -73,14 +75,16 @@
     "ember-source": "~6.0.0-beta.1",
     "ember-template-lint": "^6.0.0",
     "ember-welcome-page": "^7.0.2",
-    "eslint": "^8.57.1",
+    "eslint": "^9.12.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-ember": "^12.2.1",
-    "eslint-plugin-n": "^16.6.2",
+    "eslint-plugin-n": "^17.11.1",
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-qunit": "^8.1.2",
+    "globals": "^15.11.0",
     "loader.js": "^4.7.0",
     "prettier": "^3.3.3",
+    "prettier-plugin-ember-template-tag": "^2.0.2",
     "qunit": "^2.22.0",
     "qunit-dom": "^3.2.1",
     "stylelint": "^16.10.0",
@@ -88,6 +92,7 @@
     "stylelint-prettier": "^5.0.2",
     "tracked-built-ins": "^3.3.0",
     "typescript": "^5.6.2",
+    "typescript-eslint": "^8.8.1",
     "webpack": "^5.95.0"
   },
   "engines": {
diff --git a/tests/fixtures/app/typescript-embroider/tsconfig.json b/tests/fixtures/app/typescript-embroider/tsconfig.json
index f017b5390..e61dd08cc 100644
--- a/tests/fixtures/app/typescript-embroider/tsconfig.json
+++ b/tests/fixtures/app/typescript-embroider/tsconfig.json
@@ -11,7 +11,7 @@
       "*": ["types/*"]
     },
     "types": [
-      "ember-source/types",
+      "./node_modules/ember-source/types/stable",
       "./node_modules/ember-data/unstable-preview-types",
       "./node_modules/@ember-data/store/unstable-preview-types",
       "./node_modules/@ember-data/adapter/unstable-preview-types",
diff --git a/tests/fixtures/app/typescript/.eslintrc.js b/tests/fixtures/app/typescript/.eslintrc.js
deleted file mode 100644
index 121ac75c9..000000000
--- a/tests/fixtures/app/typescript/.eslintrc.js
+++ /dev/null
@@ -1,55 +0,0 @@
-'use strict';
-
-module.exports = {
-  root: true,
-  parser: '@typescript-eslint/parser',
-  parserOptions: {
-    ecmaVersion: 'latest',
-  },
-  plugins: ['ember', '@typescript-eslint'],
-  extends: [
-    'eslint:recommended',
-    'plugin:ember/recommended',
-    'plugin:prettier/recommended',
-  ],
-  env: {
-    browser: true,
-  },
-  rules: {},
-  overrides: [
-    // ts files
-    {
-      files: ['**/*.ts'],
-      extends: [
-        'plugin:@typescript-eslint/eslint-recommended',
-        'plugin:@typescript-eslint/recommended',
-      ],
-      rules: {},
-    },
-    // node files
-    {
-      files: [
-        './.eslintrc.js',
-        './.prettierrc.js',
-        './.stylelintrc.js',
-        './.template-lintrc.js',
-        './ember-cli-build.js',
-        './testem.js',
-        './blueprints/*/index.js',
-        './config/**/*.js',
-        './lib/*/index.js',
-        './server/**/*.js',
-      ],
-      env: {
-        browser: false,
-        node: true,
-      },
-      extends: ['plugin:n/recommended'],
-    },
-    {
-      // test files
-      files: ['tests/**/*-test.{js,ts}'],
-      extends: ['plugin:qunit/recommended'],
-    },
-  ],
-};
diff --git a/tests/fixtures/app/typescript/eslint.config.mjs b/tests/fixtures/app/typescript/eslint.config.mjs
new file mode 100644
index 000000000..8acc12d10
--- /dev/null
+++ b/tests/fixtures/app/typescript/eslint.config.mjs
@@ -0,0 +1,155 @@
+import globals from 'globals';
+import js from '@eslint/js';
+
+import ts from 'typescript-eslint';
+
+import ember from 'eslint-plugin-ember';
+import emberRecommended from 'eslint-plugin-ember/configs/recommended';
+import gjsRecommended from 'eslint-plugin-ember/configs/recommended-gjs';
+import gtsRecommended from 'eslint-plugin-ember/configs/recommended-gts';
+
+import prettier from 'eslint-plugin-prettier/recommended';
+import qunit from 'eslint-plugin-qunit';
+import n from 'eslint-plugin-n';
+
+import emberParser from 'ember-eslint-parser';
+import babelParser from '@babel/eslint-parser';
+
+const parserOptions = {
+  esm: {
+    js: {
+      ecmaFeatures: { modules: true },
+      ecmaVersion: 'latest',
+      requireConfigFile: false,
+      babelOptions: {
+        plugins: [
+          [
+            '@babel/plugin-proposal-decorators',
+            { decoratorsBeforeExport: true },
+          ],
+        ],
+      },
+    },
+    ts: {
+      projectService: true,
+      tsconfigRootDir: import.meta.dirname,
+    },
+  },
+};
+
+export default ts.config(
+  js.configs.recommended,
+  prettier,
+  {
+    files: ['**/*.js'],
+    languageOptions: {
+      parser: babelParser,
+      parserOptions: parserOptions.esm.js,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+    },
+  },
+  {
+    files: ['**/*.ts'],
+    plugins: { ember },
+    languageOptions: {
+      parserOptions: parserOptions.esm.ts,
+    },
+    extends: [...ts.configs.strictTypeChecked, ...emberRecommended],
+  },
+  {
+    files: ['**/*.gjs'],
+    languageOptions: {
+      parser: emberParser,
+      parserOptions: parserOptions.esm.js,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+      ...gjsRecommended.rules,
+    },
+  },
+  {
+    files: ['**/*.gts'],
+    plugins: { ember },
+    languageOptions: {
+      parserOptions: parserOptions.esm.ts,
+    },
+    extends: [
+      ...ts.configs.strictTypeChecked,
+      ...emberRecommended,
+      ...gtsRecommended,
+    ],
+  },
+  {
+    files: ['tests/**/*-test.{js,gjs}'],
+    plugins: {
+      qunit,
+    },
+  },
+  /**
+   * CJS node files
+   */
+  {
+    files: [
+      '**/*.cjs',
+      'config/**/*.js',
+      'testem.js',
+      'testem*.js',
+      '.prettierrc.js',
+      '.stylelintrc.js',
+      '.template-lintrc.js',
+      'ember-cli-build.js',
+    ],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'script',
+      ecmaVersion: 'latest',
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+  /**
+   * ESM node files
+   */
+  {
+    files: ['*.mjs'],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'module',
+      ecmaVersion: 'latest',
+      parserOptions: parserOptions.esm.js,
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+  /**
+   * Settings
+   */
+  {
+    ignores: ['dist/', 'node_modules/', 'coverage/', '!**/.*'],
+    linterOptions: {
+      reportUnusedDisableDirectives: 'error',
+    },
+  }
+);
diff --git a/tests/fixtures/app/typescript/package.json b/tests/fixtures/app/typescript/package.json
index 3760e9d35..4c38fd811 100644
--- a/tests/fixtures/app/typescript/package.json
+++ b/tests/fixtures/app/typescript/package.json
@@ -40,15 +40,17 @@
     "@ember/optional-features": "^2.1.0",
     "@ember/string": "^4.0.0",
     "@ember/test-helpers": "^4.0.4",
+    "@eslint/js": "^9.12.0",
     "@glimmer/component": "^1.1.2",
     "@glimmer/tracking": "^1.1.2",
     "@glint/environment-ember-loose": "^1.4.0",
     "@glint/template": "^1.4.0",
     "@tsconfig/ember": "^3.0.8",
+    "@types/eslint__js": "^8.42.3",
     "@types/qunit": "^2.19.10",
     "@types/rsvp": "^4.0.9",
-    "@typescript-eslint/eslint-plugin": "^7.18.0",
-    "@typescript-eslint/parser": "^7.18.0",
+    "@typescript-eslint/eslint-plugin": "^8.8.1",
+    "@typescript-eslint/parser": "^8.8.1",
     "@warp-drive/core-types": "~0.0.0-beta.11",
     "broccoli-asset-rev": "^3.0.0",
     "concurrently": "^9.0.1",
@@ -72,14 +74,16 @@
     "ember-source": "~6.0.0-beta.1",
     "ember-template-lint": "^6.0.0",
     "ember-welcome-page": "^7.0.2",
-    "eslint": "^8.57.1",
+    "eslint": "^9.12.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-ember": "^12.2.1",
-    "eslint-plugin-n": "^16.6.2",
+    "eslint-plugin-n": "^17.11.1",
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-qunit": "^8.1.2",
+    "globals": "^15.11.0",
     "loader.js": "^4.7.0",
     "prettier": "^3.3.3",
+    "prettier-plugin-ember-template-tag": "^2.0.2",
     "qunit": "^2.22.0",
     "qunit-dom": "^3.2.1",
     "stylelint": "^16.10.0",
@@ -87,6 +91,7 @@
     "stylelint-prettier": "^5.0.2",
     "tracked-built-ins": "^3.3.0",
     "typescript": "^5.6.2",
+    "typescript-eslint": "^8.8.1",
     "webpack": "^5.95.0"
   },
   "engines": {
diff --git a/tests/fixtures/app/typescript/tsconfig.json b/tests/fixtures/app/typescript/tsconfig.json
index f017b5390..e61dd08cc 100644
--- a/tests/fixtures/app/typescript/tsconfig.json
+++ b/tests/fixtures/app/typescript/tsconfig.json
@@ -11,7 +11,7 @@
       "*": ["types/*"]
     },
     "types": [
-      "ember-source/types",
+      "./node_modules/ember-source/types/stable",
       "./node_modules/ember-data/unstable-preview-types",
       "./node_modules/@ember-data/store/unstable-preview-types",
       "./node_modules/@ember-data/adapter/unstable-preview-types",
diff --git a/tests/fixtures/app/with-blueprint-override-lint-fail/.eslintrc.js b/tests/fixtures/app/with-blueprint-override-lint-fail/.eslintrc.js
deleted file mode 100644
index 8738a8cb4..000000000
--- a/tests/fixtures/app/with-blueprint-override-lint-fail/.eslintrc.js
+++ /dev/null
@@ -1,51 +0,0 @@
-'use strict';
-
-module.exports = {
-  root: true,
-  parser: '@babel/eslint-parser',
-  parserOptions: {
-    ecmaVersion: 'latest',
-    sourceType: 'module',
-    requireConfigFile: false,
-    babelOptions: {
-      plugins: [
-        ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }],
-      ],
-    },
-  },
-  plugins: ['ember'],
-  extends: [
-    'eslint:recommended',
-    'plugin:ember/recommended',
-    'plugin:prettier/recommended',
-  ],
-  env: {
-    browser: true,
-  },
-  rules: {},
-  overrides: [
-    // node files
-    {
-      files: [
-        './.eslintrc.js',
-        './.prettierrc.js',
-        './.stylelintrc.js',
-        './.template-lintrc.js',
-        './ember-cli-build.js',
-        './testem.js',
-        './blueprints/*/index.js',
-        './config/**/*.js',
-        './lib/*/index.js',
-        './server/**/*.js',
-      ],
-      parserOptions: {
-        sourceType: 'script',
-      },
-      env: {
-        browser: false,
-        node: true,
-      },
-      extends: ['plugin:n/recommended'],
-    },
-  ],
-};
diff --git a/tests/fixtures/app/with-blueprint-override-lint-fail/eslint.config.mjs b/tests/fixtures/app/with-blueprint-override-lint-fail/eslint.config.mjs
new file mode 100644
index 000000000..e05042fd2
--- /dev/null
+++ b/tests/fixtures/app/with-blueprint-override-lint-fail/eslint.config.mjs
@@ -0,0 +1,119 @@
+import globals from 'globals';
+import js from '@eslint/js';
+
+import ember from 'eslint-plugin-ember';
+import emberRecommended from 'eslint-plugin-ember/configs/recommended';
+import gjsRecommended from 'eslint-plugin-ember/configs/recommended-gjs';
+
+import prettier from 'eslint-plugin-prettier/recommended';
+import qunit from 'eslint-plugin-qunit';
+import n from 'eslint-plugin-n';
+
+import emberParser from 'ember-eslint-parser';
+import babelParser from '@babel/eslint-parser';
+
+const esmParserOptions = {
+  ecmaFeatures: { modules: true },
+  ecmaVersion: 'latest',
+  requireConfigFile: false,
+  babelOptions: {
+    plugins: [
+      ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }],
+    ],
+  },
+};
+
+export default [
+  js.configs.recommended,
+  prettier,
+  {
+    ignores: ['dist/', 'node_modules/', 'coverage/', '!**/.*'],
+    linterOptions: {
+      reportUnusedDisableDirectives: 'error',
+    },
+  },
+  {
+    files: ['**/*.js'],
+    languageOptions: {
+      parser: babelParser,
+      parserOptions: esmParserOptions,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+      ...gjsRecommended.rules,
+    },
+  },
+  {
+    files: ['**/*.gjs'],
+    languageOptions: {
+      parser: emberParser,
+      parserOptions: esmParserOptions,
+      globals: {
+        ...globals.browser,
+      },
+    },
+    plugins: {
+      ember,
+    },
+    rules: {
+      ...emberRecommended.rules,
+      ...gjsRecommended.rules,
+    },
+  },
+  {
+    files: ['tests/**/*-test.{js,gjs}'],
+    plugins: {
+      qunit,
+    },
+  },
+  /**
+   * CJS node files
+   */
+  {
+    files: [
+      '**/*.cjs',
+      'config/**/*.js',
+      'testem.js',
+      'testem*.js',
+      '.prettierrc.js',
+      '.stylelintrc.js',
+      '.template-lintrc.js',
+      'ember-cli-build.js',
+    ],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'script',
+      ecmaVersion: 'latest',
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+  /**
+   * ESM node files
+   */
+  {
+    files: ['*.mjs'],
+    plugins: {
+      n,
+    },
+
+    languageOptions: {
+      sourceType: 'module',
+      ecmaVersion: 'latest',
+      parserOptions: esmParserOptions,
+      globals: {
+        ...globals.node,
+      },
+    },
+  },
+];
diff --git a/tests/fixtures/app/yarn/package.json b/tests/fixtures/app/yarn/package.json
index d7047d43d..d2e6a7458 100644
--- a/tests/fixtures/app/yarn/package.json
+++ b/tests/fixtures/app/yarn/package.json
@@ -31,6 +31,7 @@
     "@ember/optional-features": "^2.1.0",
     "@ember/string": "^4.0.0",
     "@ember/test-helpers": "^4.0.4",
+    "@eslint/js": "^9.12.0",
     "@glimmer/component": "^1.1.2",
     "@glimmer/tracking": "^1.1.2",
     "broccoli-asset-rev": "^3.0.0",
@@ -55,14 +56,16 @@
     "ember-source": "~6.0.0-beta.1",
     "ember-template-lint": "^6.0.0",
     "ember-welcome-page": "^7.0.2",
-    "eslint": "^8.57.1",
+    "eslint": "^9.12.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-ember": "^12.2.1",
-    "eslint-plugin-n": "^16.6.2",
+    "eslint-plugin-n": "^17.11.1",
     "eslint-plugin-prettier": "^5.2.1",
     "eslint-plugin-qunit": "^8.1.2",
+    "globals": "^15.11.0",
     "loader.js": "^4.7.0",
     "prettier": "^3.3.3",
+    "prettier-plugin-ember-template-tag": "^2.0.2",
     "qunit": "^2.22.0",
     "qunit-dom": "^3.2.1",
     "stylelint": "^16.10.0",