@next/codemod 16.4.0-canary.55 → 16.4.0-canary.56

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.
@@ -45,6 +45,8 @@ program
45
45
  .option('--verbose', 'Verbose output', false)
46
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)
47
47
  .option('--skip-adoption', 'Skip optional feature-adoption codemods while applying version migrations.', false)
48
+ .option('--skip-react-upgrade', 'Keep React dependencies, types, and overrides unchanged and skip React codemods.', false)
49
+ .option('--skip-eslint-upgrade', 'Keep ESLint dependencies unchanged and skip the next-lint-to-eslint-cli codemod.', false)
48
50
  .action(async (revision, options) => {
49
51
  try {
50
52
  await (0, upgrade_1.runUpgrade)(revision, options);
package/bin/transform.js CHANGED
@@ -109,7 +109,10 @@ async function runTransform(transform, path, options) {
109
109
  const execaChildProcess = (0, execa_1.default)(exports.jscodeshiftExecutable, args, {
110
110
  // include ANSI color codes
111
111
  // Note: execa merges env with existing env by default.
112
- env: process.stdout.isTTY ? { FORCE_COLOR: 'true' } : {},
112
+ env: {
113
+ ...process.env,
114
+ ...(process.stdout.isTTY ? { FORCE_COLOR: 'true' } : {}),
115
+ },
113
116
  });
114
117
  // "\n" + "a\n" + "b\n"
115
118
  let lastThreeLineBreaks = '';
package/bin/upgrade.js CHANGED
@@ -190,6 +190,7 @@ async function runUpgrade(revision, options) {
190
190
  // x-ref(release): https://github.com/vercel/next.js/releases/tag/v14.3.0-canary.45
191
191
  (0, semver_1.compare)(targetNextVersion, '14.3.0-canary.45') >= 0 &&
192
192
  installedReactVersion.startsWith('18') &&
193
+ !options.skipReactUpgrade &&
193
194
  // Pure App Router always uses React 19
194
195
  // The mixed case is tricky to handle from a types perspective.
195
196
  // We'll recommend to upgrade in the prompt but users can decide to try 18.
@@ -218,9 +219,11 @@ async function runUpgrade(revision, options) {
218
219
  // E.g. in peerDependencies we could have `^18.2.0 || ^19.0.0 || 20.0.0-canary`
219
220
  // If we'd just `npm add` that, the manifest would read the same version query.
220
221
  // This is basically a `npm --save-exact react@$versionQuery` that works for every package manager.
221
- const targetReactVersion = shouldStayOnReact18
222
- ? '18.3.1'
223
- : await loadHighestNPMVersionMatching(`react@${targetNextPackageJson.peerDependencies['react']}`);
222
+ const targetReactVersion = options.skipReactUpgrade
223
+ ? installedReactVersion
224
+ : shouldStayOnReact18
225
+ ? '18.3.1'
226
+ : await loadHighestNPMVersionMatching(`react@${targetNextPackageJson.peerDependencies['react']}`);
224
227
  if ((0, semver_1.compare)(targetNextVersion, '15.0.0-canary') >= 0 &&
225
228
  (0, semver_1.compare)(targetNextVersion, '16.0.0-canary') < 0) {
226
229
  await suggestTurbopack(appPackageJson, targetNextVersion, nonInteractive);
@@ -231,7 +234,8 @@ async function runUpgrade(revision, options) {
231
234
  let shouldRunReactTypesCodemods = false;
232
235
  let execCommand = 'npx --yes';
233
236
  // The following React codemods are for React 19
234
- if (!shouldStayOnReact18 &&
237
+ if (!options.skipReactUpgrade &&
238
+ !shouldStayOnReact18 &&
235
239
  (0, semver_1.compare)(targetReactVersion, '19.0.0-0') >= 0 &&
236
240
  (0, semver_1.compare)(installedReactVersion, '19.0.0-0') < 0) {
237
241
  shouldRunReactCodemods = await suggestReactCodemods(nonInteractive);
@@ -248,19 +252,31 @@ async function runUpgrade(revision, options) {
248
252
  };
249
253
  const versionMapping = {
250
254
  next: { version: targetNextVersion, required: true },
251
- react: { version: targetReactVersion, required: true },
252
- 'react-dom': { version: targetReactVersion, required: true },
253
- 'react-is': { version: targetReactVersion, required: false },
254
255
  };
256
+ // Recovery retries must preserve the repaired React declarations and pins.
257
+ if (!options.skipReactUpgrade) {
258
+ Object.assign(versionMapping, {
259
+ react: { version: targetReactVersion, required: true },
260
+ 'react-dom': { version: targetReactVersion, required: true },
261
+ 'react-is': { version: targetReactVersion, required: false },
262
+ });
263
+ }
255
264
  for (const optionalNextjsPackage of optionalNextjsPackages) {
265
+ // Preserve the lint stack together; its newer config can require newer ESLint.
266
+ if (options.skipEslintUpgrade &&
267
+ (optionalNextjsPackage === 'eslint-config-next' ||
268
+ optionalNextjsPackage === '@next/eslint-plugin-next')) {
269
+ continue;
270
+ }
256
271
  versionMapping[optionalNextjsPackage] = {
257
272
  version: targetNextVersion,
258
273
  required: false,
259
274
  };
260
275
  }
261
- if (targetReactVersion.startsWith('19.0.0-canary') ||
262
- targetReactVersion.startsWith('19.0.0-beta') ||
263
- targetReactVersion.startsWith('19.0.0-rc')) {
276
+ if (!options.skipReactUpgrade &&
277
+ (targetReactVersion.startsWith('19.0.0-canary') ||
278
+ targetReactVersion.startsWith('19.0.0-beta') ||
279
+ targetReactVersion.startsWith('19.0.0-rc'))) {
264
280
  const [targetReactTypesVersion, targetReactDOMTypesVersion] = await Promise.all([
265
281
  loadHighestNPMVersionMatching(`types-react@rc`),
266
282
  loadHighestNPMVersionMatching(`types-react-dom@rc`),
@@ -278,7 +294,7 @@ async function runUpgrade(revision, options) {
278
294
  };
279
295
  }
280
296
  }
281
- else {
297
+ else if (!options.skipReactUpgrade) {
282
298
  const [targetReactTypesVersion, targetReactDOMTypesVersion] = await Promise.all([
283
299
  loadHighestNPMVersionMatching(`@types/react@${targetNextPackageJson.peerDependencies['react']}`),
284
300
  loadHighestNPMVersionMatching(`@types/react-dom@${targetNextPackageJson.peerDependencies['react']}`),
@@ -306,7 +322,9 @@ async function runUpgrade(revision, options) {
306
322
  // Only act when the project is actually using `eslint-config-next` — we
307
323
  // don't want to silently upgrade eslint majors for projects that use
308
324
  // eslint for unrelated reasons.
309
- if (allDependencies['eslint'] && allDependencies['eslint-config-next']) {
325
+ if (!options.skipEslintUpgrade &&
326
+ allDependencies['eslint'] &&
327
+ allDependencies['eslint-config-next']) {
310
328
  try {
311
329
  const eslintConfigNextPeerDepsJSON = (0, child_process_1.execSync)(`npm --silent view "eslint-config-next@${targetNextVersion}" peerDependencies --json`, { encoding: 'utf-8' });
312
330
  const eslintConfigNextPeerDeps = eslintConfigNextPeerDepsJSON.trim() === ''
@@ -336,10 +354,10 @@ async function runUpgrade(revision, options) {
336
354
  // Even though we only need those if we alias `@types/react` to types-react,
337
355
  // we still do it out of safety due to https://github.com/microsoft/DefinitelyTyped-tools/issues/433.
338
356
  const overrides = {};
339
- if (allDependencies['@types/react']) {
357
+ if (!options.skipReactUpgrade && allDependencies['@types/react']) {
340
358
  overrides['@types/react'] = versionMapping['@types/react'].version;
341
359
  }
342
- if (allDependencies['@types/react-dom']) {
360
+ if (!options.skipReactUpgrade && allDependencies['@types/react-dom']) {
343
361
  overrides['@types/react-dom'] = versionMapping['@types/react-dom'].version;
344
362
  }
345
363
  writeOverridesField(appPackageJson, packageManager, overrides);
@@ -362,7 +380,12 @@ async function runUpgrade(revision, options) {
362
380
  // Common IDE formatters would add a newline as well.
363
381
  os.EOL);
364
382
  (0, handle_package_1.runInstallation)(packageManager, { cwd });
365
- for (const codemod of codemods) {
383
+ // Opting out skips the lint migration as well as its dependency upgrades.
384
+ const selectedCodemods = codemods.filter((codemod) => !options.skipEslintUpgrade || codemod !== 'next-lint-to-eslint-cli');
385
+ if (selectedCodemods.length !== codemods.length) {
386
+ console.log('Skipping next-lint-to-eslint-cli. Complete any required lint migration manually before verification.');
387
+ }
388
+ for (const codemod of selectedCodemods) {
366
389
  await (0, transform_1.runTransform)(codemod, cwd, {
367
390
  force: true,
368
391
  verbose,
@@ -395,7 +418,7 @@ async function runUpgrade(revision, options) {
395
418
  });
396
419
  }
397
420
  console.log(); // new line
398
- if (codemods.length > 0) {
421
+ if (selectedCodemods.length > 0) {
399
422
  console.log(`${picocolors_1.default.green('✔')} Codemods have been applied successfully.`);
400
423
  }
401
424
  try {
@@ -13,6 +13,7 @@ exports.addPackageDependency = addPackageDependency;
13
13
  const find_up_1 = __importDefault(require("find-up"));
14
14
  const execa_1 = __importDefault(require("execa"));
15
15
  const node_child_process_1 = require("node:child_process");
16
+ const node_fs_1 = require("node:fs");
16
17
  const node_path_1 = require("node:path");
17
18
  /**
18
19
  * Get the full version string for the given package manager.
@@ -57,6 +58,17 @@ function getPnpmMajorVersion() {
57
58
  }
58
59
  function getPkgManager(baseDir) {
59
60
  try {
61
+ // The app's declared manager takes precedence over the command launcher.
62
+ const manifestPath = (0, node_path_1.join)(baseDir, 'package.json');
63
+ if ((0, node_fs_1.existsSync)(manifestPath)) {
64
+ const { packageManager } = JSON.parse((0, node_fs_1.readFileSync)(manifestPath, 'utf8'));
65
+ const match = typeof packageManager === 'string'
66
+ ? packageManager.match(/^(npm|pnpm|yarn|bun)@/)
67
+ : null;
68
+ if (match) {
69
+ return match[1];
70
+ }
71
+ }
60
72
  const userAgent = process.env.npm_config_user_agent;
61
73
  if (userAgent) {
62
74
  if (userAgent.startsWith('yarn')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next/codemod",
3
- "version": "16.4.0-canary.55",
3
+ "version": "16.4.0-canary.56",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",