@next/codemod 16.0.0-canary.8 → 16.0.0-canary.9
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.
- package/package.json +1 -1
- package/transforms/next-lint-to-eslint-cli.js +419 -322
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@ exports.prefixes = void 0;
|
|
|
7
7
|
exports.default = transformer;
|
|
8
8
|
const node_fs_1 = require("node:fs");
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
10
11
|
const handle_package_1 = require("../lib/handle-package");
|
|
11
12
|
const parser_1 = require("../lib/parser");
|
|
12
13
|
const picocolors_1 = require("picocolors");
|
|
@@ -19,20 +20,12 @@ exports.prefixes = {
|
|
|
19
20
|
event: (0, picocolors_1.green)((0, picocolors_1.bold)('✓')),
|
|
20
21
|
trace: (0, picocolors_1.magenta)((0, picocolors_1.bold)('»')),
|
|
21
22
|
};
|
|
22
|
-
const ESLINT_CONFIG_TEMPLATE_TYPESCRIPT =
|
|
23
|
-
import
|
|
24
|
-
import { fileURLToPath } from "url";
|
|
25
|
-
import { FlatCompat } from "@eslint/eslintrc";
|
|
26
|
-
|
|
27
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
28
|
-
const __dirname = dirname(__filename);
|
|
29
|
-
|
|
30
|
-
const compat = new FlatCompat({
|
|
31
|
-
baseDirectory: __dirname,
|
|
32
|
-
});
|
|
23
|
+
const ESLINT_CONFIG_TEMPLATE_TYPESCRIPT = `import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
|
|
24
|
+
import nextTypescript from "eslint-config-next/typescript";
|
|
33
25
|
|
|
34
26
|
const eslintConfig = [
|
|
35
|
-
...
|
|
27
|
+
...nextCoreWebVitals,
|
|
28
|
+
...nextTypescript,
|
|
36
29
|
{
|
|
37
30
|
ignores: [
|
|
38
31
|
"node_modules/**",
|
|
@@ -46,19 +39,10 @@ const eslintConfig = [
|
|
|
46
39
|
|
|
47
40
|
export default eslintConfig;
|
|
48
41
|
`;
|
|
49
|
-
const ESLINT_CONFIG_TEMPLATE_JAVASCRIPT = `import
|
|
50
|
-
import { fileURLToPath } from "url";
|
|
51
|
-
import { FlatCompat } from "@eslint/eslintrc";
|
|
52
|
-
|
|
53
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
54
|
-
const __dirname = dirname(__filename);
|
|
55
|
-
|
|
56
|
-
const compat = new FlatCompat({
|
|
57
|
-
baseDirectory: __dirname,
|
|
58
|
-
});
|
|
42
|
+
const ESLINT_CONFIG_TEMPLATE_JAVASCRIPT = `import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
|
|
59
43
|
|
|
60
44
|
const eslintConfig = [
|
|
61
|
-
...
|
|
45
|
+
...nextCoreWebVitals,
|
|
62
46
|
{
|
|
63
47
|
ignores: [
|
|
64
48
|
"node_modules/**",
|
|
@@ -96,19 +80,19 @@ function findExistingEslintConfig(projectRoot) {
|
|
|
96
80
|
for (const config of flatConfigs) {
|
|
97
81
|
const configPath = node_path_1.default.join(projectRoot, config);
|
|
98
82
|
if ((0, node_fs_1.existsSync)(configPath)) {
|
|
99
|
-
return { exists: true, path: configPath,
|
|
83
|
+
return { exists: true, path: configPath, isLegacy: false };
|
|
100
84
|
}
|
|
101
85
|
}
|
|
102
86
|
// Check for legacy configs
|
|
103
87
|
for (const config of legacyConfigs) {
|
|
104
88
|
const configPath = node_path_1.default.join(projectRoot, config);
|
|
105
89
|
if ((0, node_fs_1.existsSync)(configPath)) {
|
|
106
|
-
return { exists: true, path: configPath,
|
|
90
|
+
return { exists: true, path: configPath, isLegacy: true };
|
|
107
91
|
}
|
|
108
92
|
}
|
|
109
|
-
return { exists: false };
|
|
93
|
+
return { exists: false, path: null, isLegacy: null };
|
|
110
94
|
}
|
|
111
|
-
function
|
|
95
|
+
function replaceFlatCompatInConfig(configPath) {
|
|
112
96
|
let configContent;
|
|
113
97
|
try {
|
|
114
98
|
configContent = (0, node_fs_1.readFileSync)(configPath, 'utf8');
|
|
@@ -117,114 +101,277 @@ function updateExistingFlatConfig(configPath, isTypeScript) {
|
|
|
117
101
|
console.error(` Error reading config file: ${error}`);
|
|
118
102
|
return false;
|
|
119
103
|
}
|
|
120
|
-
// Check if
|
|
121
|
-
const
|
|
122
|
-
configContent.includes('
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
configPath.endsWith('.mts') ||
|
|
126
|
-
configPath.endsWith('.cts')) {
|
|
127
|
-
console.warn(exports.prefixes.warn, ' TypeScript config files require manual migration');
|
|
128
|
-
console.log(' Please add the following to your config:');
|
|
129
|
-
console.log(' - Import: import { FlatCompat } from "@eslint/eslintrc"');
|
|
130
|
-
console.log(' - Extend: ...compat.extends("next/core-web-vitals"' +
|
|
131
|
-
(isTypeScript ? ', "next/typescript"' : '') +
|
|
132
|
-
')');
|
|
104
|
+
// Check if FlatCompat is used
|
|
105
|
+
const hasFlatCompat = configContent.includes('FlatCompat') ||
|
|
106
|
+
configContent.includes('@eslint/eslintrc');
|
|
107
|
+
if (!hasFlatCompat) {
|
|
108
|
+
console.log(' No FlatCompat usage found, no changes needed');
|
|
133
109
|
return false;
|
|
134
110
|
}
|
|
135
111
|
// Parse the file using jscodeshift
|
|
136
112
|
const j = (0, parser_1.createParserFromPath)(configPath);
|
|
137
113
|
const root = j(configContent);
|
|
138
|
-
//
|
|
139
|
-
let
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
114
|
+
// Track if we need to add imports and preserve other configs
|
|
115
|
+
let needsNextVitals = false;
|
|
116
|
+
let needsNextTs = false;
|
|
117
|
+
let otherConfigs = [];
|
|
118
|
+
// Look for FlatCompat extends usage and identify which configs are being used
|
|
119
|
+
root.find(j.CallExpression).forEach((astPath) => {
|
|
120
|
+
const node = astPath.value;
|
|
121
|
+
if (node.callee.type === 'MemberExpression' &&
|
|
122
|
+
node.callee.object.type === 'Identifier' &&
|
|
123
|
+
node.callee.object.name === 'compat' &&
|
|
124
|
+
node.callee.property.type === 'Identifier' &&
|
|
125
|
+
node.callee.property.name === 'extends') {
|
|
126
|
+
// Check arguments for all configs
|
|
127
|
+
node.arguments.forEach((arg) => {
|
|
128
|
+
if (arg.type === 'Literal' || arg.type === 'StringLiteral') {
|
|
129
|
+
if (arg.value === 'next/core-web-vitals') {
|
|
130
|
+
needsNextVitals = true;
|
|
131
|
+
}
|
|
132
|
+
else if (arg.value === 'next/typescript') {
|
|
133
|
+
needsNextTs = true;
|
|
134
|
+
}
|
|
135
|
+
else if (typeof arg.value === 'string') {
|
|
136
|
+
// Preserve other configs (non-Next.js or other Next.js variants)
|
|
137
|
+
otherConfigs.push(arg.value);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
if (!needsNextVitals && !needsNextTs && otherConfigs.length === 0) {
|
|
144
|
+
console.warn(exports.prefixes.warn, ' No ESLint configs found in FlatCompat usage');
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
if (!needsNextVitals && !needsNextTs) {
|
|
148
|
+
console.log(' No Next.js configs found, but preserving other configs');
|
|
149
|
+
}
|
|
150
|
+
// Only remove FlatCompat setup if no other configs need it
|
|
151
|
+
if (otherConfigs.length === 0) {
|
|
152
|
+
// Remove FlatCompat imports and setup
|
|
153
|
+
root.find(j.ImportDeclaration).forEach((astPath) => {
|
|
154
|
+
const node = astPath.value;
|
|
155
|
+
if (node.source.value === '@eslint/eslintrc' ||
|
|
156
|
+
node.source.value === '@eslint/js') {
|
|
157
|
+
// Only remove FlatCompat-specific imports
|
|
158
|
+
j(astPath).remove();
|
|
154
159
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
160
|
+
// Leave path/url imports alone - they might be used elsewhere
|
|
161
|
+
});
|
|
162
|
+
// Remove only the compat variable - keep __dirname and __filename
|
|
163
|
+
root.find(j.VariableDeclaration).forEach((astPath) => {
|
|
164
|
+
const node = astPath.value;
|
|
165
|
+
if (node.declarations) {
|
|
166
|
+
// Filter out only the compat variable
|
|
167
|
+
const filteredDeclarations = node.declarations.filter((decl) => {
|
|
168
|
+
if (decl && decl.id && decl.id.type === 'Identifier') {
|
|
169
|
+
return decl.id.name !== 'compat';
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
});
|
|
173
|
+
if (filteredDeclarations.length === 0) {
|
|
174
|
+
// Remove entire declaration if no declarations left
|
|
175
|
+
j(astPath).remove();
|
|
176
|
+
}
|
|
177
|
+
else if (filteredDeclarations.length < node.declarations.length) {
|
|
178
|
+
// Update declaration with filtered declarations
|
|
179
|
+
node.declarations = filteredDeclarations;
|
|
180
|
+
}
|
|
158
181
|
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
console.log(' Preserving FlatCompat setup for other ESLint configs');
|
|
186
|
+
}
|
|
187
|
+
// Add new imports after the eslint/config import
|
|
188
|
+
const imports = [];
|
|
189
|
+
// Add imports in correct order: core-web-vitals first, then typescript
|
|
190
|
+
if (needsNextVitals) {
|
|
191
|
+
imports.push(j.importDeclaration([j.importDefaultSpecifier(j.identifier('nextCoreWebVitals'))], j.literal('eslint-config-next/core-web-vitals')));
|
|
192
|
+
}
|
|
193
|
+
if (needsNextTs) {
|
|
194
|
+
imports.push(j.importDeclaration([j.importDefaultSpecifier(j.identifier('nextTypescript'))], j.literal('eslint-config-next/typescript')));
|
|
195
|
+
}
|
|
196
|
+
// Find the eslint/config import and insert our imports after it
|
|
197
|
+
let eslintConfigImportPath = null;
|
|
198
|
+
root.find(j.ImportDeclaration).forEach((astPath) => {
|
|
199
|
+
if (astPath.value.source.value === 'eslint/config') {
|
|
200
|
+
eslintConfigImportPath = astPath;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
// Insert imports after eslint/config import (or at beginning if not found)
|
|
204
|
+
if (eslintConfigImportPath) {
|
|
205
|
+
// Insert after the eslint/config import in correct order
|
|
206
|
+
for (let i = imports.length - 1; i >= 0; i--) {
|
|
207
|
+
eslintConfigImportPath.insertAfter(imports[i]);
|
|
159
208
|
}
|
|
160
|
-
catch {
|
|
161
|
-
// Default to CommonJS if package.json can't be read
|
|
162
|
-
isCommonJS = true;
|
|
163
|
-
}
|
|
164
|
-
// Always check file syntax to override package.json detection if needed
|
|
165
|
-
// This handles cases where package.json doesn't specify type but file uses ES modules
|
|
166
|
-
const hasESModuleSyntax = root.find(j.ExportDefaultDeclaration).size() > 0 ||
|
|
167
|
-
root.find(j.ExportNamedDeclaration).size() > 0 ||
|
|
168
|
-
root.find(j.ImportDeclaration).size() > 0;
|
|
169
|
-
const hasCommonJSSyntax = root
|
|
170
|
-
.find(j.AssignmentExpression, {
|
|
171
|
-
left: {
|
|
172
|
-
type: 'MemberExpression',
|
|
173
|
-
object: { name: 'module' },
|
|
174
|
-
property: { name: 'exports' },
|
|
175
|
-
},
|
|
176
|
-
})
|
|
177
|
-
.size() > 0;
|
|
178
|
-
// Override package.json detection based on actual syntax
|
|
179
|
-
if (hasESModuleSyntax && !hasCommonJSSyntax) {
|
|
180
|
-
isCommonJS = false;
|
|
181
|
-
}
|
|
182
|
-
else if (hasCommonJSSyntax && !hasESModuleSyntax) {
|
|
183
|
-
isCommonJS = true;
|
|
184
|
-
}
|
|
185
|
-
// If both or neither are found, keep the package.json-based detection
|
|
186
209
|
}
|
|
187
210
|
else {
|
|
188
|
-
//
|
|
189
|
-
|
|
211
|
+
// Fallback: insert at the beginning in correct order
|
|
212
|
+
const program = root.find(j.Program);
|
|
213
|
+
for (let i = imports.length - 1; i >= 0; i--) {
|
|
214
|
+
program.get('body', 0).insertBefore(imports[i]);
|
|
215
|
+
}
|
|
190
216
|
}
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
217
|
+
// Replace FlatCompat extends with spread imports
|
|
218
|
+
root.find(j.SpreadElement).forEach((astPath) => {
|
|
219
|
+
const node = astPath.value;
|
|
220
|
+
if (node.argument.type === 'CallExpression' &&
|
|
221
|
+
node.argument.callee.type === 'MemberExpression' &&
|
|
222
|
+
node.argument.callee.object.type === 'Identifier' &&
|
|
223
|
+
node.argument.callee.object.name === 'compat' &&
|
|
224
|
+
node.argument.callee.property.type === 'Identifier' &&
|
|
225
|
+
node.argument.callee.property.name === 'extends') {
|
|
226
|
+
// Replace with spread of direct imports and preserve other configs
|
|
227
|
+
const replacements = [];
|
|
228
|
+
node.argument.arguments.forEach((arg) => {
|
|
229
|
+
if (arg.type === 'Literal' || arg.type === 'StringLiteral') {
|
|
230
|
+
if (arg.value === 'next/core-web-vitals') {
|
|
231
|
+
replacements.push(j.spreadElement(j.identifier('nextCoreWebVitals')));
|
|
232
|
+
}
|
|
233
|
+
else if (arg.value === 'next/typescript') {
|
|
234
|
+
replacements.push(j.spreadElement(j.identifier('nextTypescript')));
|
|
235
|
+
}
|
|
236
|
+
else if (typeof arg.value === 'string') {
|
|
237
|
+
// Preserve other configs as compat.extends() calls
|
|
238
|
+
replacements.push(j.spreadElement(j.callExpression(j.memberExpression(j.identifier('compat'), j.identifier('extends')), [j.literal(arg.value)])));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
if (replacements.length > 0) {
|
|
243
|
+
// Replace the current spread element with multiple spread elements
|
|
244
|
+
const parent = astPath.parent;
|
|
245
|
+
if (parent.value.type === 'ArrayExpression') {
|
|
246
|
+
const index = parent.value.elements.indexOf(node);
|
|
247
|
+
if (index !== -1) {
|
|
248
|
+
parent.value.elements.splice(index, 1, ...replacements);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
// Also handle the case where extends is used as a property value (not spread)
|
|
255
|
+
root.find(j.ObjectExpression).forEach((astPath) => {
|
|
256
|
+
const objectNode = astPath.value;
|
|
257
|
+
objectNode.properties?.forEach((prop) => {
|
|
258
|
+
if (prop.type === 'ObjectProperty' &&
|
|
259
|
+
prop.key.type === 'Identifier' &&
|
|
260
|
+
prop.key.name === 'extends' &&
|
|
261
|
+
prop.value.type === 'CallExpression' &&
|
|
262
|
+
prop.value.callee.type === 'MemberExpression' &&
|
|
263
|
+
prop.value.callee.object.type === 'Identifier' &&
|
|
264
|
+
prop.value.callee.object.name === 'compat' &&
|
|
265
|
+
prop.value.callee.property.type === 'Identifier' &&
|
|
266
|
+
prop.value.callee.property.name === 'extends') {
|
|
267
|
+
// Replace with array of spread imports and preserve other configs
|
|
268
|
+
const replacements = [];
|
|
269
|
+
prop.value.arguments.forEach((arg) => {
|
|
270
|
+
if (arg.type === 'Literal' || arg.type === 'StringLiteral') {
|
|
271
|
+
if (arg.value === 'next/core-web-vitals') {
|
|
272
|
+
replacements.push(j.spreadElement(j.identifier('nextCoreWebVitals')));
|
|
273
|
+
}
|
|
274
|
+
else if (arg.value === 'next/typescript') {
|
|
275
|
+
replacements.push(j.spreadElement(j.identifier('nextTypescript')));
|
|
276
|
+
}
|
|
277
|
+
else if (typeof arg.value === 'string') {
|
|
278
|
+
// Preserve other configs as compat.extends() calls
|
|
279
|
+
replacements.push(j.spreadElement(j.callExpression(j.memberExpression(j.identifier('compat'), j.identifier('extends')), [j.literal(arg.value)])));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
if (replacements.length > 0) {
|
|
284
|
+
// Replace the property value with an array of spreads
|
|
285
|
+
prop.value = j.arrayExpression(replacements);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
203
288
|
});
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
289
|
+
});
|
|
290
|
+
// Generate the updated code
|
|
291
|
+
const updatedContent = root.toSource();
|
|
292
|
+
if (updatedContent !== configContent) {
|
|
293
|
+
// Validate the generated code by parsing it
|
|
294
|
+
try {
|
|
295
|
+
const validateJ = (0, parser_1.createParserFromPath)(configPath);
|
|
296
|
+
validateJ(updatedContent); // This will throw if the syntax is invalid
|
|
297
|
+
}
|
|
298
|
+
catch (parseError) {
|
|
299
|
+
console.error(` Generated code has invalid syntax: ${parseError instanceof Error ? parseError.message : parseError}`);
|
|
300
|
+
console.error(' Skipping update to prevent breaking the config file');
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
// Create backup of original file
|
|
304
|
+
const backupPath = `${configPath}.backup-${Date.now()}`;
|
|
305
|
+
try {
|
|
306
|
+
(0, node_fs_1.writeFileSync)(backupPath, configContent);
|
|
307
|
+
}
|
|
308
|
+
catch (backupError) {
|
|
309
|
+
console.warn(` Warning: Could not create backup file: ${backupError}`);
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
(0, node_fs_1.writeFileSync)(configPath, updatedContent);
|
|
313
|
+
console.log(` Updated ${node_path_1.default.basename(configPath)} to use direct eslint-config-next imports`);
|
|
314
|
+
// Remove backup on success
|
|
315
|
+
try {
|
|
316
|
+
if ((0, node_fs_1.existsSync)(backupPath)) {
|
|
317
|
+
(0, node_fs_1.unlinkSync)(backupPath);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch (cleanupError) {
|
|
321
|
+
console.warn(` Warning: Could not remove backup file ${backupPath}: ${cleanupError}`);
|
|
322
|
+
}
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
console.error(` Error writing config file: ${error}`);
|
|
327
|
+
// Restore from backup on failure
|
|
328
|
+
try {
|
|
329
|
+
if ((0, node_fs_1.existsSync)(backupPath)) {
|
|
330
|
+
(0, node_fs_1.writeFileSync)(configPath, (0, node_fs_1.readFileSync)(backupPath, 'utf8'));
|
|
331
|
+
console.log(' Restored original config from backup');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
catch (restoreError) {
|
|
335
|
+
console.error(` Error restoring backup: ${restoreError}`);
|
|
336
|
+
}
|
|
337
|
+
return false;
|
|
207
338
|
}
|
|
208
339
|
}
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
function updateExistingFlatConfig(configPath, isTypeScript = false) {
|
|
343
|
+
let configContent;
|
|
344
|
+
try {
|
|
345
|
+
configContent = (0, node_fs_1.readFileSync)(configPath, 'utf8');
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
console.error(` Error reading config file: ${error}`);
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
// Check if Next.js configs are already imported directly
|
|
352
|
+
const hasNextVitals = configContent.includes('eslint-config-next/core-web-vitals');
|
|
353
|
+
const hasNextTs = configContent.includes('eslint-config-next/typescript');
|
|
354
|
+
const hasNextConfigs = hasNextVitals || hasNextTs;
|
|
355
|
+
// Parse the file using jscodeshift
|
|
356
|
+
const j = (0, parser_1.createParserFromPath)(configPath);
|
|
357
|
+
const root = j(configContent);
|
|
358
|
+
// Find the exported array - support different export patterns
|
|
359
|
+
let exportedArray = null;
|
|
360
|
+
// Pattern 1: export default [...]
|
|
361
|
+
const directArrayExports = root.find(j.ExportDefaultDeclaration, {
|
|
362
|
+
declaration: { type: 'ArrayExpression' },
|
|
363
|
+
});
|
|
364
|
+
if (directArrayExports.size() > 0) {
|
|
365
|
+
exportedArray = directArrayExports.at(0).get('declaration');
|
|
366
|
+
}
|
|
209
367
|
else {
|
|
210
|
-
//
|
|
211
|
-
const
|
|
212
|
-
declaration: { type: '
|
|
368
|
+
// Pattern 2: const config = [...]; export default config
|
|
369
|
+
const defaultExportIdentifier = root.find(j.ExportDefaultDeclaration, {
|
|
370
|
+
declaration: { type: 'Identifier' },
|
|
213
371
|
});
|
|
214
|
-
if (
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
// Look for const variable = [...]; export default variable
|
|
220
|
-
const defaultExportIdentifier = root.find(j.ExportDefaultDeclaration, {
|
|
221
|
-
declaration: { type: 'Identifier' },
|
|
222
|
-
});
|
|
223
|
-
if (defaultExportIdentifier.size() > 0) {
|
|
224
|
-
const declarationNode = defaultExportIdentifier.at(0).get('declaration');
|
|
225
|
-
if (!declarationNode.value) {
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
372
|
+
if (defaultExportIdentifier.size() > 0) {
|
|
373
|
+
const declarationNode = defaultExportIdentifier.at(0).get('declaration');
|
|
374
|
+
if (declarationNode.value) {
|
|
228
375
|
const varName = declarationNode.value.name;
|
|
229
376
|
const varDeclaration = root.find(j.VariableDeclarator, {
|
|
230
377
|
id: { name: varName },
|
|
@@ -233,201 +380,135 @@ function updateExistingFlatConfig(configPath, isTypeScript) {
|
|
|
233
380
|
if (varDeclaration.size() > 0) {
|
|
234
381
|
exportedArray = varDeclaration.at(0).get('init');
|
|
235
382
|
}
|
|
383
|
+
else {
|
|
384
|
+
// Pattern 3: defineConfig([...]) or similar wrapper function
|
|
385
|
+
const callDeclaration = root.find(j.VariableDeclarator, {
|
|
386
|
+
id: { name: varName },
|
|
387
|
+
init: { type: 'CallExpression' },
|
|
388
|
+
});
|
|
389
|
+
if (callDeclaration.size() > 0) {
|
|
390
|
+
const callExpression = callDeclaration.at(0).get('init');
|
|
391
|
+
if (callExpression.value.arguments.length > 0 &&
|
|
392
|
+
callExpression.value.arguments[0].type === 'ArrayExpression') {
|
|
393
|
+
exportedArray = callExpression.get('arguments', 0);
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
console.warn(exports.prefixes.warn, ' Wrapper function does not have an array parameter. Manual migration required.');
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
236
401
|
}
|
|
237
402
|
}
|
|
238
403
|
}
|
|
239
404
|
if (!exportedArray) {
|
|
240
|
-
console.warn(exports.prefixes.warn, ' Config does not export an array. Manual migration required.');
|
|
241
|
-
console.warn(exports.prefixes.warn, ' ESLint flat configs must export an array of configuration objects.');
|
|
405
|
+
console.warn(exports.prefixes.warn, ' Config does not export an array or supported pattern. Manual migration required.');
|
|
242
406
|
return false;
|
|
243
407
|
}
|
|
244
|
-
//
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
arguments: [{ value: '@eslint/eslintrc' }],
|
|
250
|
-
})
|
|
251
|
-
.size() > 0
|
|
252
|
-
: root
|
|
253
|
-
.find(j.ImportDeclaration, {
|
|
254
|
-
source: { value: '@eslint/eslintrc' },
|
|
255
|
-
})
|
|
256
|
-
.size() > 0;
|
|
257
|
-
// Add necessary imports if not present and if we're adding Next.js extends
|
|
258
|
-
if (!hasFlatCompat && !hasNextConfigs) {
|
|
259
|
-
if (isCommonJS) {
|
|
260
|
-
// Add CommonJS requires at the top
|
|
261
|
-
const firstNode = root.find(j.Program).get('body', 0);
|
|
262
|
-
const compatRequire = j.variableDeclaration('const', [
|
|
263
|
-
j.variableDeclarator(j.objectPattern([
|
|
264
|
-
j.property('init', j.identifier('FlatCompat'), j.identifier('FlatCompat')),
|
|
265
|
-
]), j.callExpression(j.identifier('require'), [
|
|
266
|
-
j.literal('@eslint/eslintrc'),
|
|
267
|
-
])),
|
|
268
|
-
]);
|
|
269
|
-
const pathRequire = j.variableDeclaration('const', [
|
|
270
|
-
j.variableDeclarator(j.identifier('path'), j.callExpression(j.identifier('require'), [j.literal('path')])),
|
|
271
|
-
]);
|
|
272
|
-
const compatNew = j.variableDeclaration('const', [
|
|
273
|
-
j.variableDeclarator(j.identifier('compat'), j.newExpression(j.identifier('FlatCompat'), [
|
|
274
|
-
j.objectExpression([
|
|
275
|
-
j.property('init', j.identifier('baseDirectory'), j.identifier('__dirname')),
|
|
276
|
-
]),
|
|
277
|
-
])),
|
|
278
|
-
]);
|
|
279
|
-
j(firstNode).insertBefore(compatRequire);
|
|
280
|
-
j(firstNode).insertBefore(pathRequire);
|
|
281
|
-
j(firstNode).insertBefore(compatNew);
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
// Add ES module imports
|
|
285
|
-
const firstImport = root.find(j.ImportDeclaration).at(0);
|
|
286
|
-
const insertPoint = firstImport.size() > 0
|
|
287
|
-
? firstImport
|
|
288
|
-
: root.find(j.Program).get('body', 0);
|
|
289
|
-
const imports = [
|
|
290
|
-
j.importDeclaration([j.importSpecifier(j.identifier('dirname'))], j.literal('path')),
|
|
291
|
-
j.importDeclaration([j.importSpecifier(j.identifier('fileURLToPath'))], j.literal('url')),
|
|
292
|
-
j.importDeclaration([j.importSpecifier(j.identifier('FlatCompat'))], j.literal('@eslint/eslintrc')),
|
|
293
|
-
];
|
|
294
|
-
const setupVars = [
|
|
295
|
-
j.variableDeclaration('const', [
|
|
296
|
-
j.variableDeclarator(j.identifier('__filename'), j.callExpression(j.identifier('fileURLToPath'), [
|
|
297
|
-
j.memberExpression(j.memberExpression(j.identifier('import'), j.identifier('meta')), j.identifier('url')),
|
|
298
|
-
])),
|
|
299
|
-
]),
|
|
300
|
-
j.variableDeclaration('const', [
|
|
301
|
-
j.variableDeclarator(j.identifier('__dirname'), j.callExpression(j.identifier('dirname'), [
|
|
302
|
-
j.identifier('__filename'),
|
|
303
|
-
])),
|
|
304
|
-
]),
|
|
305
|
-
j.variableDeclaration('const', [
|
|
306
|
-
j.variableDeclarator(j.identifier('compat'), j.newExpression(j.identifier('FlatCompat'), [
|
|
307
|
-
j.objectExpression([
|
|
308
|
-
j.property('init', j.identifier('baseDirectory'), j.identifier('__dirname')),
|
|
309
|
-
]),
|
|
310
|
-
])),
|
|
311
|
-
]),
|
|
312
|
-
];
|
|
313
|
-
if (firstImport.size() > 0) {
|
|
314
|
-
// Insert after the last import
|
|
315
|
-
const lastImportPath = root.find(j.ImportDeclaration).at(-1).get();
|
|
316
|
-
if (!lastImportPath) {
|
|
317
|
-
// Fallback to inserting at the beginning
|
|
318
|
-
const fallbackInsertPoint = root.find(j.Program).get('body', 0);
|
|
319
|
-
imports.forEach((imp) => j(fallbackInsertPoint).insertBefore(imp));
|
|
320
|
-
setupVars.forEach((v) => j(fallbackInsertPoint).insertBefore(v));
|
|
321
|
-
}
|
|
322
|
-
else {
|
|
323
|
-
imports.forEach((imp) => j(lastImportPath).insertAfter(imp));
|
|
324
|
-
setupVars.forEach((v) => j(lastImportPath).insertAfter(v));
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
// Insert at the beginning
|
|
329
|
-
imports.forEach((imp) => j(insertPoint).insertBefore(imp));
|
|
330
|
-
setupVars.forEach((v) => j(insertPoint).insertBefore(v));
|
|
331
|
-
}
|
|
332
|
-
}
|
|
408
|
+
// Add Next.js imports if not present
|
|
409
|
+
const program = root.find(j.Program);
|
|
410
|
+
const imports = [];
|
|
411
|
+
if (!hasNextVitals) {
|
|
412
|
+
imports.push(j.importDeclaration([j.importDefaultSpecifier(j.identifier('nextCoreWebVitals'))], j.literal('eslint-config-next/core-web-vitals')));
|
|
333
413
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
j.property('init', j.identifier('ignores'), j.arrayExpression([
|
|
337
|
-
j.literal('node_modules/**'),
|
|
338
|
-
j.literal('.next/**'),
|
|
339
|
-
j.literal('out/**'),
|
|
340
|
-
j.literal('build/**'),
|
|
341
|
-
j.literal('next-env.d.ts'),
|
|
342
|
-
])),
|
|
343
|
-
]);
|
|
344
|
-
// Only add Next.js extends if they're not already present
|
|
345
|
-
if (!hasNextConfigs) {
|
|
346
|
-
// Add Next.js configs to the array
|
|
347
|
-
const nextExtends = isTypeScript
|
|
348
|
-
? ['next/core-web-vitals', 'next/typescript']
|
|
349
|
-
: ['next/core-web-vitals'];
|
|
350
|
-
const spreadElement = j.spreadElement(j.callExpression(j.memberExpression(j.identifier('compat'), j.identifier('extends')), nextExtends.map((ext) => j.literal(ext))));
|
|
351
|
-
// Insert Next.js extends at the beginning of the array
|
|
352
|
-
if (!exportedArray.value.elements) {
|
|
353
|
-
exportedArray.value.elements = [];
|
|
354
|
-
}
|
|
355
|
-
exportedArray.value.elements.unshift(spreadElement);
|
|
356
|
-
}
|
|
357
|
-
// Check if ignores already exist in the config and merge if needed
|
|
358
|
-
let existingIgnoresIndex = -1;
|
|
359
|
-
if (exportedArray.value.elements) {
|
|
360
|
-
for (let i = 0; i < exportedArray.value.elements.length; i++) {
|
|
361
|
-
const element = exportedArray.value.elements[i];
|
|
362
|
-
if (element &&
|
|
363
|
-
element.type === 'ObjectExpression' &&
|
|
364
|
-
element.properties &&
|
|
365
|
-
element.properties.some((prop) => prop.type === 'Property' &&
|
|
366
|
-
prop.key &&
|
|
367
|
-
prop.key.type === 'Identifier' &&
|
|
368
|
-
prop.key.name === 'ignores')) {
|
|
369
|
-
existingIgnoresIndex = i;
|
|
370
|
-
break;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
414
|
+
if (!hasNextTs && isTypeScript) {
|
|
415
|
+
imports.push(j.importDeclaration([j.importDefaultSpecifier(j.identifier('nextTypescript'))], j.literal('eslint-config-next/typescript')));
|
|
373
416
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
exportedArray.value.elements.splice(insertIndex, 0, ignoresConfig);
|
|
417
|
+
// Insert imports at the beginning in correct order
|
|
418
|
+
for (let i = imports.length - 1; i >= 0; i--) {
|
|
419
|
+
program.get('body', 0).insertBefore(imports[i]);
|
|
378
420
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
421
|
+
// Add spread elements to config array if not already present
|
|
422
|
+
if (!exportedArray.value.elements) {
|
|
423
|
+
exportedArray.value.elements = [];
|
|
424
|
+
}
|
|
425
|
+
const spreadsToAdd = [];
|
|
426
|
+
if (!hasNextVitals) {
|
|
427
|
+
spreadsToAdd.push(j.spreadElement(j.identifier('nextCoreWebVitals')));
|
|
428
|
+
}
|
|
429
|
+
if (!hasNextTs && isTypeScript) {
|
|
430
|
+
spreadsToAdd.push(j.spreadElement(j.identifier('nextTypescript')));
|
|
431
|
+
}
|
|
432
|
+
// Insert at the beginning of array in correct order
|
|
433
|
+
for (let i = spreadsToAdd.length - 1; i >= 0; i--) {
|
|
434
|
+
exportedArray.value.elements.unshift(spreadsToAdd[i]);
|
|
435
|
+
}
|
|
436
|
+
// Add ignores config if not already present
|
|
437
|
+
const hasIgnores = exportedArray.value.elements.some((element) => element &&
|
|
438
|
+
element.type === 'ObjectExpression' &&
|
|
439
|
+
element.properties &&
|
|
440
|
+
element.properties.some((prop) => prop.type === 'ObjectProperty' &&
|
|
383
441
|
prop.key &&
|
|
384
442
|
prop.key.type === 'Identifier' &&
|
|
385
|
-
prop.key.name === 'ignores');
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
'
|
|
392
|
-
'
|
|
393
|
-
'
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const existingIgnores = ignoresProp.value.elements
|
|
398
|
-
.map((el) => (el.type === 'Literal' ? el.value : null))
|
|
399
|
-
.filter(Boolean);
|
|
400
|
-
for (const ignore of nextIgnores) {
|
|
401
|
-
if (!existingIgnores.includes(ignore)) {
|
|
402
|
-
ignoresProp.value.elements.push(j.literal(ignore));
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
443
|
+
prop.key.name === 'ignores'));
|
|
444
|
+
if (!hasIgnores) {
|
|
445
|
+
const ignoresConfig = j.objectExpression([
|
|
446
|
+
j.property('init', j.identifier('ignores'), j.arrayExpression([
|
|
447
|
+
j.literal('node_modules/**'),
|
|
448
|
+
j.literal('.next/**'),
|
|
449
|
+
j.literal('out/**'),
|
|
450
|
+
j.literal('build/**'),
|
|
451
|
+
j.literal('next-env.d.ts'),
|
|
452
|
+
])),
|
|
453
|
+
]);
|
|
454
|
+
exportedArray.value.elements.push(ignoresConfig);
|
|
406
455
|
}
|
|
407
456
|
// Generate the updated code
|
|
408
457
|
const updatedContent = root.toSource();
|
|
409
458
|
if (updatedContent !== configContent) {
|
|
459
|
+
// Validate the generated code by parsing it
|
|
460
|
+
try {
|
|
461
|
+
const validateJ = (0, parser_1.createParserFromPath)(configPath);
|
|
462
|
+
validateJ(updatedContent); // This will throw if the syntax is invalid
|
|
463
|
+
}
|
|
464
|
+
catch (parseError) {
|
|
465
|
+
console.error(` Generated code has invalid syntax: ${parseError instanceof Error ? parseError.message : parseError}`);
|
|
466
|
+
console.error(' Skipping update to prevent breaking the config file');
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
// Create backup of original file
|
|
470
|
+
const backupPath = `${configPath}.backup-${Date.now()}`;
|
|
471
|
+
try {
|
|
472
|
+
(0, node_fs_1.writeFileSync)(backupPath, configContent);
|
|
473
|
+
}
|
|
474
|
+
catch (backupError) {
|
|
475
|
+
console.warn(` Warning: Could not create backup file: ${backupError}`);
|
|
476
|
+
}
|
|
410
477
|
try {
|
|
411
478
|
(0, node_fs_1.writeFileSync)(configPath, updatedContent);
|
|
479
|
+
console.log(` Updated ${node_path_1.default.basename(configPath)} with Next.js configurations`);
|
|
480
|
+
// Remove backup on success
|
|
481
|
+
try {
|
|
482
|
+
if ((0, node_fs_1.existsSync)(backupPath)) {
|
|
483
|
+
(0, node_fs_1.unlinkSync)(backupPath);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
catch (cleanupError) {
|
|
487
|
+
console.warn(` Warning: Could not remove backup file ${backupPath}: ${cleanupError}`);
|
|
488
|
+
}
|
|
489
|
+
return true;
|
|
412
490
|
}
|
|
413
491
|
catch (error) {
|
|
414
492
|
console.error(` Error writing config file: ${error}`);
|
|
493
|
+
// Restore from backup on failure
|
|
494
|
+
try {
|
|
495
|
+
if ((0, node_fs_1.existsSync)(backupPath)) {
|
|
496
|
+
(0, node_fs_1.writeFileSync)(configPath, (0, node_fs_1.readFileSync)(backupPath, 'utf8'));
|
|
497
|
+
console.log(' Restored original config from backup');
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
catch (restoreError) {
|
|
501
|
+
console.error(` Error restoring backup: ${restoreError}`);
|
|
502
|
+
}
|
|
415
503
|
return false;
|
|
416
504
|
}
|
|
417
|
-
if (hasNextConfigs) {
|
|
418
|
-
console.log(` Updated ${node_path_1.default.basename(configPath)} with Next.js ignores configuration`);
|
|
419
|
-
}
|
|
420
|
-
else {
|
|
421
|
-
console.log(` Updated ${node_path_1.default.basename(configPath)} with Next.js ESLint configs`);
|
|
422
|
-
}
|
|
423
|
-
return true;
|
|
424
505
|
}
|
|
425
|
-
// If nothing changed but
|
|
506
|
+
// If nothing changed but configs are present, that's still success
|
|
426
507
|
if (hasNextConfigs) {
|
|
427
508
|
console.log(' Next.js ESLint configs already present in flat config');
|
|
428
509
|
return true;
|
|
429
510
|
}
|
|
430
|
-
return
|
|
511
|
+
return true;
|
|
431
512
|
}
|
|
432
513
|
function updatePackageJsonScripts(packageJsonContent) {
|
|
433
514
|
try {
|
|
@@ -577,10 +658,13 @@ function updatePackageJsonScripts(packageJsonContent) {
|
|
|
577
658
|
nextVersion || 'latest';
|
|
578
659
|
needsUpdate = true;
|
|
579
660
|
}
|
|
580
|
-
//
|
|
581
|
-
if (
|
|
582
|
-
|
|
583
|
-
|
|
661
|
+
// Remove @eslint/eslintrc if it exists since we no longer use FlatCompat
|
|
662
|
+
if (packageJson.devDependencies?.['@eslint/eslintrc']) {
|
|
663
|
+
delete packageJson.devDependencies['@eslint/eslintrc'];
|
|
664
|
+
needsUpdate = true;
|
|
665
|
+
}
|
|
666
|
+
if (packageJson.dependencies?.['@eslint/eslintrc']) {
|
|
667
|
+
delete packageJson.dependencies['@eslint/eslintrc'];
|
|
584
668
|
needsUpdate = true;
|
|
585
669
|
}
|
|
586
670
|
const updatedContent = `${JSON.stringify(packageJson, null, 2)}\n`;
|
|
@@ -612,36 +696,8 @@ function transformer(files, options = {}) {
|
|
|
612
696
|
console.log('Migrating from next lint to the ESLint CLI...');
|
|
613
697
|
// Check for existing ESLint config
|
|
614
698
|
const existingConfig = findExistingEslintConfig(projectRoot);
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
// Try to update existing flat config
|
|
618
|
-
if (existingConfig.path) {
|
|
619
|
-
console.log(` Found existing flat config: ${node_path_1.default.basename(existingConfig.path)}`);
|
|
620
|
-
const updated = updateExistingFlatConfig(existingConfig.path, isTypeScript);
|
|
621
|
-
if (!updated) {
|
|
622
|
-
console.log(' Could not automatically update the existing flat config.');
|
|
623
|
-
console.log(' Please manually ensure your ESLint config extends "next/core-web-vitals"');
|
|
624
|
-
if (isTypeScript) {
|
|
625
|
-
console.log(' and "next/typescript" for TypeScript projects.');
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
else {
|
|
631
|
-
// Legacy config exists
|
|
632
|
-
if (existingConfig.path) {
|
|
633
|
-
console.log(` Found legacy ESLint config: ${node_path_1.default.basename(existingConfig.path)}`);
|
|
634
|
-
console.log(' Legacy .eslintrc configs are not automatically migrated.');
|
|
635
|
-
console.log(' Please migrate to flat config format (eslint.config.js) and ensure it extends:');
|
|
636
|
-
console.log(' - "next/core-web-vitals"');
|
|
637
|
-
if (isTypeScript) {
|
|
638
|
-
console.log(' - "next/typescript"');
|
|
639
|
-
}
|
|
640
|
-
console.log(' Learn more: https://eslint.org/docs/latest/use/configure/migration-guide');
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
else {
|
|
699
|
+
// If no existing ESLint config found, create a new one.
|
|
700
|
+
if (existingConfig.exists === false) {
|
|
645
701
|
// Create new ESLint flat config
|
|
646
702
|
const eslintConfigPath = node_path_1.default.join(projectRoot, 'eslint.config.mjs');
|
|
647
703
|
const template = isTypeScript
|
|
@@ -655,7 +711,48 @@ function transformer(files, options = {}) {
|
|
|
655
711
|
console.error(' Error creating ESLint config:', error);
|
|
656
712
|
}
|
|
657
713
|
}
|
|
658
|
-
|
|
714
|
+
else {
|
|
715
|
+
let eslintConfigFilename = node_path_1.default.basename(existingConfig.path);
|
|
716
|
+
let eslintConfigPath = existingConfig.path;
|
|
717
|
+
// If legacy config found, run ESLint migration tool first. It will
|
|
718
|
+
// use FlatCompat, so will continue to migrate using Flat config format.
|
|
719
|
+
if (existingConfig.isLegacy && existingConfig.path) {
|
|
720
|
+
console.log(` Found legacy ESLint config: ${eslintConfigFilename}`);
|
|
721
|
+
// Run npx @eslint/migrate-config
|
|
722
|
+
const command = `npx @eslint/migrate-config ${existingConfig.path}`;
|
|
723
|
+
console.log(` Running "${command}" to convert legacy config...`);
|
|
724
|
+
try {
|
|
725
|
+
(0, node_child_process_1.execSync)(command, {
|
|
726
|
+
cwd: projectRoot,
|
|
727
|
+
stdio: 'pipe',
|
|
728
|
+
});
|
|
729
|
+
// The migration tool creates eslint.config.mjs by default
|
|
730
|
+
const outputPath = node_path_1.default.join(projectRoot, 'eslint.config.mjs');
|
|
731
|
+
if (!(0, node_fs_1.existsSync)(outputPath)) {
|
|
732
|
+
throw new Error(`Failed to find the expected output file "${outputPath}" generated by the migration tool.`);
|
|
733
|
+
}
|
|
734
|
+
// Use generated config will have FlatCompat, so continue to apply
|
|
735
|
+
// the next steps to it.
|
|
736
|
+
eslintConfigPath = outputPath;
|
|
737
|
+
eslintConfigFilename = node_path_1.default.basename(eslintConfigPath);
|
|
738
|
+
}
|
|
739
|
+
catch (cause) {
|
|
740
|
+
throw new Error(`Failed to run "${command}" to migrate the legacy ESLint config "${eslintConfigFilename}".\n` +
|
|
741
|
+
`Please try the migration to Flat config manually.\n` +
|
|
742
|
+
`Learn more: https://eslint.org/docs/latest/use/configure/migration-guide`, { cause });
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
console.log(` Found existing ESLint Flat config: ${eslintConfigFilename}`);
|
|
746
|
+
// First try to replace FlatCompat usage if present
|
|
747
|
+
replaceFlatCompatInConfig(eslintConfigPath);
|
|
748
|
+
// Always try to update flat config with Next.js configurations
|
|
749
|
+
// regardless of whether FlatCompat was found
|
|
750
|
+
const updated = updateExistingFlatConfig(eslintConfigPath, isTypeScript);
|
|
751
|
+
if (!updated) {
|
|
752
|
+
console.log(' Could not automatically update the existing flat config.');
|
|
753
|
+
console.log(' Please manually ensure your ESLint config includes the Next.js configurations');
|
|
754
|
+
}
|
|
755
|
+
}
|
|
659
756
|
const packageJsonContent = (0, node_fs_1.readFileSync)(packageJsonPath, 'utf8');
|
|
660
757
|
const result = updatePackageJsonScripts(packageJsonContent);
|
|
661
758
|
if (result.updated) {
|