@next/codemod 16.3.0-canary.71 → 16.3.0-canary.73
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/bin/next-codemod.js +1 -0
- package/bin/upgrade.js +94 -29
- package/package.json +1 -1
package/bin/next-codemod.js
CHANGED
|
@@ -43,6 +43,7 @@ program
|
|
|
43
43
|
.argument('[revision]', 'Specify the upgrade type ("patch", "minor", "major"), an NPM dist tag (e.g. "latest", "canary", "rc"), or an exact version (e.g. "15.0.0"). Defaults to "minor".')
|
|
44
44
|
.usage('[revision] [options]')
|
|
45
45
|
.option('--verbose', 'Verbose output', false)
|
|
46
|
+
.option('-y, --yes', 'Skip every interactive prompt and accept its default. Also auto-enabled when stdin is not a TTY (e.g. running under an agent or in CI).', false)
|
|
46
47
|
.action(async (revision, options) => {
|
|
47
48
|
try {
|
|
48
49
|
await (0, upgrade_1.runUpgrade)(revision, options);
|
package/bin/upgrade.js
CHANGED
|
@@ -114,6 +114,10 @@ function resolveSemanticRevision(revision, installedVersion) {
|
|
|
114
114
|
}
|
|
115
115
|
async function runUpgrade(revision, options) {
|
|
116
116
|
const { verbose } = options;
|
|
117
|
+
const nonInteractive = options.yes === true || !process.stdin.isTTY;
|
|
118
|
+
if (nonInteractive) {
|
|
119
|
+
console.log(` Running in non-interactive mode. Every prompt will accept its default.`);
|
|
120
|
+
}
|
|
117
121
|
const appPackageJsonPath = path_1.default.resolve(cwd, 'package.json');
|
|
118
122
|
let appPackageJson = JSON.parse(fs_1.default.readFileSync(appPackageJsonPath, 'utf8'));
|
|
119
123
|
const installedNextVersion = getInstalledNextVersion();
|
|
@@ -189,18 +193,24 @@ async function runUpgrade(revision, options) {
|
|
|
189
193
|
// The mixed case is tricky to handle from a types perspective.
|
|
190
194
|
// We'll recommend to upgrade in the prompt but users can decide to try 18.
|
|
191
195
|
!isPureAppRouter) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
196
|
+
if (nonInteractive) {
|
|
197
|
+
// Default: upgrade React past 18.
|
|
198
|
+
shouldStayOnReact18 = false;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
const shouldStayOnReact18Res = await (0, prompts_1.default)({
|
|
202
|
+
type: 'confirm',
|
|
203
|
+
name: 'shouldStayOnReact18',
|
|
204
|
+
message: `Do you prefer to stay on React 18?` +
|
|
205
|
+
(isMixedApp
|
|
206
|
+
? " Since you're using both pages/ and app/, we recommend upgrading React to use a consistent version throughout your app."
|
|
207
|
+
: ''),
|
|
208
|
+
initial: false,
|
|
209
|
+
active: 'Yes',
|
|
210
|
+
inactive: 'No',
|
|
211
|
+
}, { onCancel: utils_1.onCancel });
|
|
212
|
+
shouldStayOnReact18 = shouldStayOnReact18Res.shouldStayOnReact18;
|
|
213
|
+
}
|
|
204
214
|
}
|
|
205
215
|
// We're resolving a specific version here to avoid including "ugly" version queries
|
|
206
216
|
// in the manifest.
|
|
@@ -212,9 +222,9 @@ async function runUpgrade(revision, options) {
|
|
|
212
222
|
: await loadHighestNPMVersionMatching(`react@${targetNextPackageJson.peerDependencies['react']}`);
|
|
213
223
|
if ((0, semver_1.compare)(targetNextVersion, '15.0.0-canary') >= 0 &&
|
|
214
224
|
(0, semver_1.compare)(targetNextVersion, '16.0.0-canary') < 0) {
|
|
215
|
-
await suggestTurbopack(appPackageJson, targetNextVersion);
|
|
225
|
+
await suggestTurbopack(appPackageJson, targetNextVersion, nonInteractive);
|
|
216
226
|
}
|
|
217
|
-
const codemods = await suggestCodemods(installedNextVersion, targetNextVersion);
|
|
227
|
+
const codemods = await suggestCodemods(installedNextVersion, targetNextVersion, nonInteractive);
|
|
218
228
|
const packageManager = (0, handle_package_1.getPkgManager)(cwd);
|
|
219
229
|
let shouldRunReactCodemods = false;
|
|
220
230
|
let shouldRunReactTypesCodemods = false;
|
|
@@ -223,8 +233,9 @@ async function runUpgrade(revision, options) {
|
|
|
223
233
|
if (!shouldStayOnReact18 &&
|
|
224
234
|
(0, semver_1.compare)(targetReactVersion, '19.0.0-0') >= 0 &&
|
|
225
235
|
(0, semver_1.compare)(installedReactVersion, '19.0.0-0') < 0) {
|
|
226
|
-
shouldRunReactCodemods = await suggestReactCodemods();
|
|
227
|
-
shouldRunReactTypesCodemods =
|
|
236
|
+
shouldRunReactCodemods = await suggestReactCodemods(nonInteractive);
|
|
237
|
+
shouldRunReactTypesCodemods =
|
|
238
|
+
await suggestReactTypesCodemods(nonInteractive);
|
|
228
239
|
execCommand = getNpxCommand(packageManager);
|
|
229
240
|
}
|
|
230
241
|
fs_1.default.writeFileSync(appPackageJsonPath, JSON.stringify(appPackageJson, null, 2));
|
|
@@ -284,6 +295,37 @@ async function runUpgrade(revision, options) {
|
|
|
284
295
|
};
|
|
285
296
|
}
|
|
286
297
|
}
|
|
298
|
+
// Bump `eslint` alongside `eslint-config-next` so the install doesn't fail
|
|
299
|
+
// on a peer-dep mismatch. e.g. `eslint-config-next@16.x` requires
|
|
300
|
+
// `eslint@>=9`, but a project upgrading from Next 15 will still have
|
|
301
|
+
// `eslint@^8` from create-next-app. Skip silently if anything goes wrong;
|
|
302
|
+
// the worst case is the user hits the same peer-dep error they would have
|
|
303
|
+
// without this bump.
|
|
304
|
+
//
|
|
305
|
+
// Only act when the project is actually using `eslint-config-next` — we
|
|
306
|
+
// don't want to silently upgrade eslint majors for projects that use
|
|
307
|
+
// eslint for unrelated reasons.
|
|
308
|
+
if (allDependencies['eslint'] && allDependencies['eslint-config-next']) {
|
|
309
|
+
try {
|
|
310
|
+
const eslintConfigNextPeerDepsJSON = (0, child_process_1.execSync)(`npm --silent view "eslint-config-next@${targetNextVersion}" peerDependencies --json`, { encoding: 'utf-8' });
|
|
311
|
+
const eslintConfigNextPeerDeps = eslintConfigNextPeerDepsJSON.trim() === ''
|
|
312
|
+
? {}
|
|
313
|
+
: JSON.parse(eslintConfigNextPeerDepsJSON);
|
|
314
|
+
const eslintRange = eslintConfigNextPeerDeps?.eslint;
|
|
315
|
+
if (eslintRange) {
|
|
316
|
+
const targetEslintVersion = await loadHighestNPMVersionMatching(`eslint@${eslintRange}`);
|
|
317
|
+
versionMapping['eslint'] = {
|
|
318
|
+
version: targetEslintVersion,
|
|
319
|
+
required: false,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (e) {
|
|
324
|
+
if (verbose) {
|
|
325
|
+
console.warn(` Could not determine eslint peer range from eslint-config-next@${targetNextVersion}. Leaving eslint version alone.`, e);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
287
329
|
// Even though we only need those if we alias `@types/react` to types-react,
|
|
288
330
|
// we still do it out of safety due to https://github.com/microsoft/DefinitelyTyped-tools/issues/433.
|
|
289
331
|
const overrides = {};
|
|
@@ -320,10 +362,11 @@ async function runUpgrade(revision, options) {
|
|
|
320
362
|
// understanding of the codemods, we run all of the applicable codemods.
|
|
321
363
|
if (shouldRunReactCodemods) {
|
|
322
364
|
// https://react.dev/blog/2024/04/25/react-19-upgrade-guide#run-all-react-19-codemods
|
|
323
|
-
(0, child_process_1.execSync)(
|
|
324
365
|
// `--no-interactive` skips the interactive prompt that asks for confirmation
|
|
325
366
|
// https://github.com/codemod-com/codemod/blob/c0cf00d13161a0ec0965b6cc6bc5d54076839cc8/apps/cli/src/flags.ts#L160
|
|
326
|
-
|
|
367
|
+
// `--allow-dirty` is required because the upgrade above modified package.json
|
|
368
|
+
// and the lockfile; the recipe refuses to run on a dirty tree otherwise.
|
|
369
|
+
(0, child_process_1.execSync)(`${execCommand} codemod@latest react/19/migration-recipe --no-interactive --allow-dirty`, { stdio: 'inherit' });
|
|
327
370
|
}
|
|
328
371
|
if (shouldRunReactTypesCodemods) {
|
|
329
372
|
// https://react.dev/blog/2024/04/25/react-19-upgrade-guide#typescript-changes
|
|
@@ -383,7 +426,7 @@ function isUsingAppDir(projectPath) {
|
|
|
383
426
|
* 3. Otherwise, we ask the user to manually add `--turbopack` to their dev command,
|
|
384
427
|
* showing the current dev command as the initial value.
|
|
385
428
|
*/
|
|
386
|
-
async function suggestTurbopack(packageJson, targetNextVersion) {
|
|
429
|
+
async function suggestTurbopack(packageJson, targetNextVersion, nonInteractive) {
|
|
387
430
|
const devScript = packageJson.scripts?.['dev'];
|
|
388
431
|
// Turbopack flag was changed from `--turbo` to `--turbopack` in v15.0.1-canary.3
|
|
389
432
|
// PR: https://github.com/vercel/next.js/pull/71657
|
|
@@ -406,19 +449,28 @@ async function suggestTurbopack(packageJson, targetNextVersion) {
|
|
|
406
449
|
}
|
|
407
450
|
return;
|
|
408
451
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
452
|
+
let enable = true;
|
|
453
|
+
if (!nonInteractive) {
|
|
454
|
+
const responseTurbopack = await (0, prompts_1.default)({
|
|
455
|
+
type: 'confirm',
|
|
456
|
+
name: 'enable',
|
|
457
|
+
message: `Enable Turbopack for ${picocolors_1.default.bold('next dev')}?`,
|
|
458
|
+
initial: true,
|
|
459
|
+
}, { onCancel: utils_1.onCancel });
|
|
460
|
+
enable = responseTurbopack.enable;
|
|
461
|
+
}
|
|
462
|
+
if (!enable) {
|
|
416
463
|
return;
|
|
417
464
|
}
|
|
418
465
|
packageJson.scripts['dev'] = devScript.replace('next dev', `next dev ${turboPackFlag}`);
|
|
419
466
|
return;
|
|
420
467
|
}
|
|
421
468
|
console.log(`${picocolors_1.default.yellow('⚠')} Could not find "${picocolors_1.default.bold('next dev')}" in your dev script.`);
|
|
469
|
+
if (nonInteractive) {
|
|
470
|
+
// Without a TTY we can't ask the user for a replacement script.
|
|
471
|
+
// Keep the existing dev script untouched.
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
422
474
|
const responseCustomDevScript = await (0, prompts_1.default)({
|
|
423
475
|
type: 'text',
|
|
424
476
|
name: 'customDevScript',
|
|
@@ -428,7 +480,7 @@ async function suggestTurbopack(packageJson, targetNextVersion) {
|
|
|
428
480
|
packageJson.scripts['dev'] =
|
|
429
481
|
responseCustomDevScript.customDevScript || devScript;
|
|
430
482
|
}
|
|
431
|
-
async function suggestCodemods(initialNextVersion, targetNextVersion) {
|
|
483
|
+
async function suggestCodemods(initialNextVersion, targetNextVersion, nonInteractive) {
|
|
432
484
|
// example:
|
|
433
485
|
// codemod version: 15.0.0-canary.45
|
|
434
486
|
// 14.3 -> 15.0.0-canary.45: apply
|
|
@@ -451,6 +503,13 @@ async function suggestCodemods(initialNextVersion, targetNextVersion) {
|
|
|
451
503
|
if (relevantCodemods.length === 0) {
|
|
452
504
|
return [];
|
|
453
505
|
}
|
|
506
|
+
if (nonInteractive) {
|
|
507
|
+
// Default: apply every recommended codemod, matching `selected: true` below.
|
|
508
|
+
const all = relevantCodemods.map(({ value }) => value);
|
|
509
|
+
console.log(` Applying all ${picocolors_1.default.blue('codemods')} recommended for your upgrade:\n` +
|
|
510
|
+
all.map((value) => ` - ${value}`).join('\n'));
|
|
511
|
+
return all;
|
|
512
|
+
}
|
|
454
513
|
const { codemods } = await (0, prompts_1.default)({
|
|
455
514
|
type: 'multiselect',
|
|
456
515
|
name: 'codemods',
|
|
@@ -466,7 +525,10 @@ async function suggestCodemods(initialNextVersion, targetNextVersion) {
|
|
|
466
525
|
}, { onCancel: utils_1.onCancel });
|
|
467
526
|
return codemods;
|
|
468
527
|
}
|
|
469
|
-
async function suggestReactCodemods() {
|
|
528
|
+
async function suggestReactCodemods(nonInteractive) {
|
|
529
|
+
if (nonInteractive) {
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
470
532
|
const { runReactCodemod } = await (0, prompts_1.default)({
|
|
471
533
|
type: 'confirm',
|
|
472
534
|
name: 'runReactCodemod',
|
|
@@ -475,7 +537,10 @@ async function suggestReactCodemods() {
|
|
|
475
537
|
}, { onCancel: utils_1.onCancel });
|
|
476
538
|
return runReactCodemod;
|
|
477
539
|
}
|
|
478
|
-
async function suggestReactTypesCodemods() {
|
|
540
|
+
async function suggestReactTypesCodemods(nonInteractive) {
|
|
541
|
+
if (nonInteractive) {
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
479
544
|
const { runReactTypesCodemod } = await (0, prompts_1.default)({
|
|
480
545
|
type: 'confirm',
|
|
481
546
|
name: 'runReactTypesCodemod',
|