Skip to content

Commit 02ee8ff

Browse files
committed
feat: add a codemod to migrate from the deprecated "next lint" command
1 parent cd3a25a commit 02ee8ff

File tree

10 files changed

+386
-4
lines changed

10 files changed

+386
-4
lines changed

docs/01-app/02-guides/upgrading/codemods.mdx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,78 @@ Replacing `<transform>` and `<path>` with appropriate values.
2424

2525
## Codemods
2626

27+
### 16.0
28+
29+
#### Migrate from `next lint` to explicit ESLint configuration
30+
31+
##### `next-lint-to-explicit-eslint`
32+
33+
```bash filename="Terminal"
34+
npx @next/codemod@latest next-lint-to-explicit-eslint .
35+
```
36+
37+
This codemod migrates projects from using `next lint` to explicit ESLint configuration files. It:
38+
39+
- Creates an `eslint.config.mjs` file with Next.js recommended configurations
40+
- Updates package.json scripts to use `eslint .` instead of `next lint`
41+
- Adds necessary ESLint dependencies to package.json
42+
- Preserves existing ESLint configurations when found
43+
44+
For example:
45+
46+
```json filename="package.json"
47+
{
48+
"scripts": {
49+
"lint": "next lint"
50+
}
51+
}
52+
```
53+
54+
Becomes:
55+
56+
```json filename="package.json"
57+
{
58+
"scripts": {
59+
"lint": "eslint ."
60+
},
61+
"devDependencies": {
62+
"eslint": "^9",
63+
"eslint-config-next": "latest",
64+
"@eslint/eslintrc": "^3"
65+
}
66+
}
67+
```
68+
69+
And creates:
70+
71+
```js filename="eslint.config.mjs"
72+
import { dirname } from 'path'
73+
import { fileURLToPath } from 'url'
74+
import { FlatCompat } from '@eslint/eslintrc'
75+
76+
const __filename = fileURLToPath(import.meta.url)
77+
const __dirname = dirname(__filename)
78+
79+
const compat = new FlatCompat({
80+
baseDirectory: __dirname,
81+
})
82+
83+
const eslintConfig = [
84+
...compat.extends('next/core-web-vitals', 'next/typescript'),
85+
{
86+
ignores: [
87+
'node_modules/**',
88+
'.next/**',
89+
'out/**',
90+
'build/**',
91+
'next-env.d.ts',
92+
],
93+
},
94+
]
95+
96+
export default eslintConfig
97+
```
98+
2799
### 15.0
28100
29101
#### Transform App Router Route Segment Config `runtime` value from `experimental-edge` to `edge`

packages/next-codemod/bin/transform.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ export async function runTransform(
113113
return require(transformerPath).default(filesExpanded, options)
114114
}
115115

116+
if (transformer === 'next-lint-to-explicit-eslint') {
117+
// next-lint-to-explicit-eslint transform doesn't use jscodeshift directly
118+
return require(transformerPath).default(filesExpanded, options)
119+
}
120+
116121
let args = []
117122

118123
const { dry, print, runInBand, jscodeshift, verbose } = options

packages/next-codemod/lib/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,9 @@ export const TRANSFORMER_INQUIRER_CHOICES = [
121121
value: 'next-experimental-turbo-to-turbopack',
122122
version: '10.0.0',
123123
},
124+
{
125+
title: 'Migrate from `next lint` to explicit ESLint configuration',
126+
value: 'next-lint-to-explicit-eslint',
127+
version: '16.0.0',
128+
},
124129
]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "my-app",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "next dev",
7+
"build": "next build",
8+
"start": "next start",
9+
"lint": "next lint"
10+
},
11+
"dependencies": {
12+
"react": "^18.3.0",
13+
"react-dom": "^18.3.0",
14+
"next": "15.0.0"
15+
},
16+
"devDependencies": {
17+
"typescript": "^5",
18+
"@types/node": "^20",
19+
"@types/react": "^19",
20+
"@types/react-dom": "^19"
21+
}
22+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "my-app",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "next dev",
7+
"build": "next build",
8+
"start": "next start",
9+
"lint": "eslint ."
10+
},
11+
"dependencies": {
12+
"react": "^18.3.0",
13+
"react-dom": "^18.3.0",
14+
"next": "15.0.0"
15+
},
16+
"devDependencies": {
17+
"typescript": "^5",
18+
"@types/node": "^20",
19+
"@types/react": "^19",
20+
"@types/react-dom": "^19",
21+
"eslint": "^9",
22+
"eslint-config-next": "15.0.0",
23+
"@eslint/eslintrc": "^3"
24+
}
25+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "my-app",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "next dev",
7+
"build": "next build",
8+
"start": "next start",
9+
"lint": "next lint"
10+
},
11+
"dependencies": {
12+
"react": "^18.3.0",
13+
"react-dom": "^18.3.0",
14+
"next": "15.0.0"
15+
},
16+
"devDependencies": {
17+
"eslint": "^8.0.0",
18+
"eslint-config-next": "15.0.0"
19+
}
20+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "my-app",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "next dev",
7+
"build": "next build",
8+
"start": "next start",
9+
"lint": "eslint ."
10+
},
11+
"dependencies": {
12+
"react": "^18.3.0",
13+
"react-dom": "^18.3.0",
14+
"next": "15.0.0"
15+
},
16+
"devDependencies": {
17+
"eslint": "^8.0.0",
18+
"eslint-config-next": "15.0.0",
19+
"@eslint/eslintrc": "^3"
20+
}
21+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* global jest */
2+
import { defineTest } from 'jscodeshift/dist/testUtils'
3+
4+
jest.autoMockOff()
5+
6+
const transform = 'next-lint-to-explicit-eslint'
7+
const fixtures = [
8+
'basic',
9+
'existing-eslint',
10+
]
11+
12+
for (const fixture of fixtures) {
13+
defineTest(__dirname, transform, {}, `${transform}/${fixture}`, {
14+
parser: 'json',
15+
})
16+
}

0 commit comments

Comments
 (0)