@next/codemod 16.3.0-canary.71 → 16.3.0-canary.72

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.
@@ -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
- const shouldStayOnReact18Res = await (0, prompts_1.default)({
193
- type: 'confirm',
194
- name: 'shouldStayOnReact18',
195
- message: `Do you prefer to stay on React 18?` +
196
- (isMixedApp
197
- ? " Since you're using both pages/ and app/, we recommend upgrading React to use a consistent version throughout your app."
198
- : ''),
199
- initial: false,
200
- active: 'Yes',
201
- inactive: 'No',
202
- }, { onCancel: utils_1.onCancel });
203
- shouldStayOnReact18 = shouldStayOnReact18Res.shouldStayOnReact18;
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 = await suggestReactTypesCodemods();
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));
@@ -320,10 +331,11 @@ async function runUpgrade(revision, options) {
320
331
  // understanding of the codemods, we run all of the applicable codemods.
321
332
  if (shouldRunReactCodemods) {
322
333
  // https://react.dev/blog/2024/04/25/react-19-upgrade-guide#run-all-react-19-codemods
323
- (0, child_process_1.execSync)(
324
334
  // `--no-interactive` skips the interactive prompt that asks for confirmation
325
335
  // https://github.com/codemod-com/codemod/blob/c0cf00d13161a0ec0965b6cc6bc5d54076839cc8/apps/cli/src/flags.ts#L160
326
- `${execCommand} codemod@latest react/19/migration-recipe --no-interactive`, { stdio: 'inherit' });
336
+ // `--allow-dirty` is required because the upgrade above modified package.json
337
+ // and the lockfile; the recipe refuses to run on a dirty tree otherwise.
338
+ (0, child_process_1.execSync)(`${execCommand} codemod@latest react/19/migration-recipe --no-interactive --allow-dirty`, { stdio: 'inherit' });
327
339
  }
328
340
  if (shouldRunReactTypesCodemods) {
329
341
  // https://react.dev/blog/2024/04/25/react-19-upgrade-guide#typescript-changes
@@ -383,7 +395,7 @@ function isUsingAppDir(projectPath) {
383
395
  * 3. Otherwise, we ask the user to manually add `--turbopack` to their dev command,
384
396
  * showing the current dev command as the initial value.
385
397
  */
386
- async function suggestTurbopack(packageJson, targetNextVersion) {
398
+ async function suggestTurbopack(packageJson, targetNextVersion, nonInteractive) {
387
399
  const devScript = packageJson.scripts?.['dev'];
388
400
  // Turbopack flag was changed from `--turbo` to `--turbopack` in v15.0.1-canary.3
389
401
  // PR: https://github.com/vercel/next.js/pull/71657
@@ -406,19 +418,28 @@ async function suggestTurbopack(packageJson, targetNextVersion) {
406
418
  }
407
419
  return;
408
420
  }
409
- const responseTurbopack = await (0, prompts_1.default)({
410
- type: 'confirm',
411
- name: 'enable',
412
- message: `Enable Turbopack for ${picocolors_1.default.bold('next dev')}?`,
413
- initial: true,
414
- }, { onCancel: utils_1.onCancel });
415
- if (!responseTurbopack.enable) {
421
+ let enable = true;
422
+ if (!nonInteractive) {
423
+ const responseTurbopack = await (0, prompts_1.default)({
424
+ type: 'confirm',
425
+ name: 'enable',
426
+ message: `Enable Turbopack for ${picocolors_1.default.bold('next dev')}?`,
427
+ initial: true,
428
+ }, { onCancel: utils_1.onCancel });
429
+ enable = responseTurbopack.enable;
430
+ }
431
+ if (!enable) {
416
432
  return;
417
433
  }
418
434
  packageJson.scripts['dev'] = devScript.replace('next dev', `next dev ${turboPackFlag}`);
419
435
  return;
420
436
  }
421
437
  console.log(`${picocolors_1.default.yellow('⚠')} Could not find "${picocolors_1.default.bold('next dev')}" in your dev script.`);
438
+ if (nonInteractive) {
439
+ // Without a TTY we can't ask the user for a replacement script.
440
+ // Keep the existing dev script untouched.
441
+ return;
442
+ }
422
443
  const responseCustomDevScript = await (0, prompts_1.default)({
423
444
  type: 'text',
424
445
  name: 'customDevScript',
@@ -428,7 +449,7 @@ async function suggestTurbopack(packageJson, targetNextVersion) {
428
449
  packageJson.scripts['dev'] =
429
450
  responseCustomDevScript.customDevScript || devScript;
430
451
  }
431
- async function suggestCodemods(initialNextVersion, targetNextVersion) {
452
+ async function suggestCodemods(initialNextVersion, targetNextVersion, nonInteractive) {
432
453
  // example:
433
454
  // codemod version: 15.0.0-canary.45
434
455
  // 14.3 -> 15.0.0-canary.45: apply
@@ -451,6 +472,13 @@ async function suggestCodemods(initialNextVersion, targetNextVersion) {
451
472
  if (relevantCodemods.length === 0) {
452
473
  return [];
453
474
  }
475
+ if (nonInteractive) {
476
+ // Default: apply every recommended codemod, matching `selected: true` below.
477
+ const all = relevantCodemods.map(({ value }) => value);
478
+ console.log(` Applying all ${picocolors_1.default.blue('codemods')} recommended for your upgrade:\n` +
479
+ all.map((value) => ` - ${value}`).join('\n'));
480
+ return all;
481
+ }
454
482
  const { codemods } = await (0, prompts_1.default)({
455
483
  type: 'multiselect',
456
484
  name: 'codemods',
@@ -466,7 +494,10 @@ async function suggestCodemods(initialNextVersion, targetNextVersion) {
466
494
  }, { onCancel: utils_1.onCancel });
467
495
  return codemods;
468
496
  }
469
- async function suggestReactCodemods() {
497
+ async function suggestReactCodemods(nonInteractive) {
498
+ if (nonInteractive) {
499
+ return true;
500
+ }
470
501
  const { runReactCodemod } = await (0, prompts_1.default)({
471
502
  type: 'confirm',
472
503
  name: 'runReactCodemod',
@@ -475,7 +506,10 @@ async function suggestReactCodemods() {
475
506
  }, { onCancel: utils_1.onCancel });
476
507
  return runReactCodemod;
477
508
  }
478
- async function suggestReactTypesCodemods() {
509
+ async function suggestReactTypesCodemods(nonInteractive) {
510
+ if (nonInteractive) {
511
+ return true;
512
+ }
479
513
  const { runReactTypesCodemod } = await (0, prompts_1.default)({
480
514
  type: 'confirm',
481
515
  name: 'runReactTypesCodemod',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next/codemod",
3
- "version": "16.3.0-canary.71",
3
+ "version": "16.3.0-canary.72",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",