@next/codemod 16.4.0-canary.3 → 16.4.0-canary.31

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/README.md CHANGED
@@ -7,3 +7,14 @@ Codemods are transformations that run on your codebase programmatically. This al
7
7
  ## Documentation
8
8
 
9
9
  Visit [nextjs.org/docs/advanced-features/codemods](https://nextjs.org/docs/app/guides/upgrading/codemods) to view the documentation for this package.
10
+
11
+ ## Skip optional feature adoption
12
+
13
+ `upgrade --skip-adoption` skips codemods marked as feature adoption in the
14
+ registry while keeping version migrations and normal dependency selection.
15
+ Currently this excludes `cache-components-instant-false` and the
16
+ `remove-partial-prefetch` cleanup used after adopting partial prefetching.
17
+ Both transforms can still be run explicitly.
18
+
19
+ Combine it with `--yes` for an unattended version upgrade. Without
20
+ `--skip-adoption`, the existing upgrade selections are unchanged.
@@ -44,6 +44,7 @@ program
44
44
  .usage('[revision] [options]')
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
+ .option('--skip-adoption', 'Skip optional feature-adoption codemods while applying version migrations.', false)
47
48
  .action(async (revision, options) => {
48
49
  try {
49
50
  await (0, upgrade_1.runUpgrade)(revision, options);
package/bin/transform.js CHANGED
@@ -56,7 +56,7 @@ async function runTransform(transform, path, options) {
56
56
  }, { onCancel: utils_1.onCancel });
57
57
  transformer = res.transformer;
58
58
  }
59
- if (transformer === 'next-request-geo-ip') {
59
+ if (transformer === 'next-request-geo-ip' && !options.nonInteractive) {
60
60
  const { isAppDeployedToVercel } = await (0, prompts_1.default)({
61
61
  type: 'confirm',
62
62
  name: 'isAppDeployedToVercel',
@@ -99,7 +99,7 @@ async function runTransform(transform, path, options) {
99
99
  args.push('--parser=tsx');
100
100
  args.push('--ignore-pattern=**/node_modules/**');
101
101
  args.push('--ignore-pattern=**/.next/**');
102
- args.push('--extensions=tsx,ts,jsx,js');
102
+ args.push('--extensions=tsx,ts,jsx,js,mjs');
103
103
  args = args.concat(['--transform', transformerPath]);
104
104
  if (jscodeshift) {
105
105
  args = args.concat(jscodeshift);
package/bin/upgrade.js CHANGED
@@ -225,7 +225,7 @@ async function runUpgrade(revision, options) {
225
225
  (0, semver_1.compare)(targetNextVersion, '16.0.0-canary') < 0) {
226
226
  await suggestTurbopack(appPackageJson, targetNextVersion, nonInteractive);
227
227
  }
228
- const codemods = await suggestCodemods(installedNextVersion, targetNextVersion, nonInteractive);
228
+ const codemods = await suggestCodemods(installedNextVersion, targetNextVersion, nonInteractive, options.skipAdoption);
229
229
  const packageManager = (0, handle_package_1.getPkgManager)(cwd);
230
230
  let shouldRunReactCodemods = false;
231
231
  let shouldRunReactTypesCodemods = false;
@@ -357,7 +357,11 @@ async function runUpgrade(revision, options) {
357
357
  os.EOL);
358
358
  (0, handle_package_1.runInstallation)(packageManager, { cwd });
359
359
  for (const codemod of codemods) {
360
- await (0, transform_1.runTransform)(codemod, cwd, { force: true, verbose });
360
+ await (0, transform_1.runTransform)(codemod, cwd, {
361
+ force: true,
362
+ verbose,
363
+ nonInteractive,
364
+ });
361
365
  }
362
366
  // To reduce user-side burden of selecting which codemods to run as it needs additional
363
367
  // understanding of the codemods, we run all of the applicable codemods.
@@ -489,7 +493,7 @@ async function suggestTurbopack(packageJson, targetNextVersion, nonInteractive)
489
493
  packageJson.scripts['dev'] =
490
494
  responseCustomDevScript.customDevScript || devScript;
491
495
  }
492
- async function suggestCodemods(initialNextVersion, targetNextVersion, nonInteractive) {
496
+ async function suggestCodemods(initialNextVersion, targetNextVersion, nonInteractive, skipAdoption = false) {
493
497
  // example:
494
498
  // codemod version: 15.0.0-canary.45
495
499
  // 14.3 -> 15.0.0-canary.45: apply
@@ -508,7 +512,7 @@ async function suggestCodemods(initialNextVersion, targetNextVersion, nonInterac
508
512
  if (targetVersionIndex === -1) {
509
513
  targetVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.length;
510
514
  }
511
- const relevantCodemods = utils_1.TRANSFORMER_INQUIRER_CHOICES.slice(initialVersionIndex, targetVersionIndex);
515
+ const relevantCodemods = utils_1.TRANSFORMER_INQUIRER_CHOICES.slice(initialVersionIndex, targetVersionIndex).filter((codemod) => !skipAdoption || !codemod.adoption);
512
516
  if (relevantCodemods.length === 0) {
513
517
  return [];
514
518
  }
package/lib/utils.js CHANGED
@@ -3,11 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.TRANSFORMER_INQUIRER_CHOICES = void 0;
6
+ exports.TRANSFORMER_INQUIRER_CHOICES = exports.NEXT_CODEMOD_IGNORE_ERROR_PREFIX = exports.NEXT_CODEMOD_ERROR_PREFIX = void 0;
7
7
  exports.checkGitStatus = checkGitStatus;
8
8
  exports.onCancel = onCancel;
9
9
  const picocolors_1 = require("picocolors");
10
10
  const is_git_clean_1 = __importDefault(require("is-git-clean"));
11
+ exports.NEXT_CODEMOD_ERROR_PREFIX = '@next-codemod-error';
12
+ exports.NEXT_CODEMOD_IGNORE_ERROR_PREFIX = '@next-codemod-ignore';
11
13
  function checkGitStatus(force) {
12
14
  let clean = false;
13
15
  let errorMessage = 'Unable to determine if git directory is clean';
@@ -136,11 +138,13 @@ exports.TRANSFORMER_INQUIRER_CHOICES = [
136
138
  title: 'Add `export const instant = false` to App Router pages and layouts to ease Cache Components adoption',
137
139
  value: 'cache-components-instant-false',
138
140
  version: '16.3.0',
141
+ adoption: true,
139
142
  },
140
143
  {
141
144
  title: "Remove `export const prefetch = 'partial'` Route Segment Config from App Router pages and layouts after enabling `partialPrefetching` globally",
142
145
  value: 'remove-partial-prefetch',
143
146
  version: '16.3.0',
147
+ adoption: true,
144
148
  },
145
149
  ];
146
150
  //# sourceMappingURL=utils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next/codemod",
3
- "version": "16.4.0-canary.3",
3
+ "version": "16.4.0-canary.31",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = transformer;
4
4
  const parser_1 = require("../lib/parser");
5
+ const utils_1 = require("../lib/utils");
5
6
  /**
6
7
  * Blanket-inserts `export const instant = false` into every App Router `page`,
7
8
  * `layout`, and `default` file so they're marked as allowed to block when
@@ -86,15 +87,16 @@ function transformer(file, _api) {
86
87
  if (hasInstantBinding) {
87
88
  return file.source;
88
89
  }
89
- // Build `export const instant = false`. The two `//` comments above it
90
- // (TODO + See:) are attached as leading comments on the declaration so
90
+ // Build `export const instant = false`. The ignore reason, removal condition,
91
+ // and guide link are attached as leading comments on the declaration so
91
92
  // recast prints them right above it.
92
- const todoComment = j.commentLine(' TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.', true, false);
93
+ const ignoreComment = j.commentLine(` ${utils_1.NEXT_CODEMOD_IGNORE_ERROR_PREFIX} Cache Components adoption: this segment temporarily allows blocking.`, true, false);
94
+ const removalComment = j.commentLine(' Remove this opt-out after verifying the segment passes validation without it.', true, false);
93
95
  const seeComment = j.commentLine(' See: https://nextjs.org/docs/app/guides/migrating-to-cache-components', true, false);
94
96
  const instantExport = j.exportNamedDeclaration(j.variableDeclaration('const', [
95
97
  j.variableDeclarator(j.identifier('instant'), j.booleanLiteral(false)),
96
98
  ]));
97
- instantExport.comments = [todoComment, seeComment];
99
+ instantExport.comments = [ignoreComment, removalComment, seeComment];
98
100
  // Insert after the last top-level import, or at the top of the module
99
101
  // if there are no imports.
100
102
  const body = program.body;
@@ -110,14 +112,19 @@ function transformer(file, _api) {
110
112
  // No imports. Inserting at index 0 would steal any file-level leading
111
113
  // comments (e.g. `// @ts-nocheck`) from `body[0]` because recast
112
114
  // attributes them to whatever is first. Move those leading comments
113
- // off `body[0]` onto the new export *before* its TODO/See: lines, so
115
+ // off `body[0]` onto the new export *before* its adoption comments, so
114
116
  // they print in their original position.
115
117
  const first = body[0];
116
118
  const allComments = (first.comments ?? []);
117
119
  const firstLeading = allComments.filter((c) => c.leading === true);
118
120
  if (firstLeading.length > 0) {
119
121
  first.comments = allComments.filter((c) => c.leading !== true);
120
- instantExport.comments = [...firstLeading, todoComment, seeComment];
122
+ instantExport.comments = [
123
+ ...firstLeading,
124
+ ignoreComment,
125
+ removalComment,
126
+ seeComment,
127
+ ];
121
128
  }
122
129
  body.unshift(instantExport);
123
130
  }
@@ -3,7 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.transformDynamicAPI = transformDynamicAPI;
4
4
  const utils_1 = require("./utils");
5
5
  const parser_1 = require("../../../lib/parser");
6
- const DYNAMIC_IMPORT_WARN_COMMENT = ` @next-codemod-error The APIs under 'next/headers' are async now, need to be manually awaited. `;
6
+ const utils_2 = require("../../../lib/utils");
7
+ const DYNAMIC_IMPORT_WARN_COMMENT = ` ${utils_2.NEXT_CODEMOD_ERROR_PREFIX} The APIs under 'next/headers' are async now, need to be manually awaited. `;
7
8
  function findDynamicImportsAndComment(root, j) {
8
9
  let modified = false;
9
10
  // find all the dynamic imports of `next/headers`,
@@ -146,12 +147,12 @@ function transformDynamicAPI(source, _api, filePath) {
146
147
  needsReactUseImport = true;
147
148
  }
148
149
  else {
149
- const casted = castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes, ` ${utils_1.NEXT_CODEMOD_ERROR_PREFIX} Manually await this call and refactor the function to be async `);
150
+ const casted = castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes, ` ${utils_2.NEXT_CODEMOD_ERROR_PREFIX} Manually await this call and refactor the function to be async `);
150
151
  modified ||= casted;
151
152
  }
152
153
  }
153
154
  else {
154
- const casted = castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes, ` ${utils_1.NEXT_CODEMOD_ERROR_PREFIX} please manually await this call, codemod cannot transform due to undetermined async scope `);
155
+ const casted = castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes, ` ${utils_2.NEXT_CODEMOD_ERROR_PREFIX} please manually await this call, codemod cannot transform due to undetermined async scope `);
155
156
  modified ||= casted;
156
157
  }
157
158
  }
@@ -227,6 +228,18 @@ function castTypesOrAddComment(j, path, originRequestApiName, root, filePath, in
227
228
  e.g. `<path>` is cookies(), convert it to `(<path> as unknown as UnsafeUnwrappedCookies)`
228
229
  */
229
230
  const targetType = API_CAST_TYPE_MAP[originRequestApiName];
231
+ const repairComment = ` ${utils_2.NEXT_CODEMOD_ERROR_PREFIX} Await this API and update its callers; remove the temporary ${targetType} cast after repairing the migration. `;
232
+ const parentCast = path.parentPath?.node;
233
+ const outerCast = path.parentPath?.parentPath?.node;
234
+ if (j.TSAsExpression.check(parentCast) &&
235
+ j.TSAsExpression.check(outerCast) &&
236
+ j.TSTypeReference.check(outerCast.typeAnnotation) &&
237
+ j.Identifier.check(outerCast.typeAnnotation.typeName) &&
238
+ outerCast.typeAnnotation.typeName.name === targetType) {
239
+ // Re-parsing attaches the leading marker to the outer cast.
240
+ return (0, utils_1.insertCommentOnce)(outerCast, j, repairComment);
241
+ }
242
+ (0, utils_1.insertCommentOnce)(path.node, j, repairComment);
230
243
  const newCastExpression = j.tsAsExpression(j.tsAsExpression(path.node, j.tsUnknownKeyword()), j.tsTypeReference(j.identifier(targetType)));
231
244
  // Replace the original expression with the new cast expression,
232
245
  // also wrap () around the new cast expression.
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.transformDynamicProps = transformDynamicProps;
4
4
  const utils_1 = require("./utils");
5
5
  const parser_1 = require("../../../lib/parser");
6
+ const utils_2 = require("../../../lib/utils");
6
7
  const PAGE_PROPS = 'props';
7
8
  // Find all the member access of the prop, and await them
8
9
  // e.g. If there's argument `props`, find all the member access of props.<name>.
@@ -40,7 +41,7 @@ function awaitMemberAccessOfProp(propIdName, path, j) {
40
41
  parentScopeOfMemberAccess.node !== path.node) {
41
42
  // If it's not able to convert, add a comment to the prop access to warn the user
42
43
  // e.g. the parent scope is sync, await keyword can't be applied
43
- const comment = ` ${utils_1.NEXT_CODEMOD_ERROR_PREFIX} '${propIdName}.${memberProperty.name}' is accessed without awaiting.`;
44
+ const comment = ` ${utils_2.NEXT_CODEMOD_ERROR_PREFIX} '${propIdName}.${memberProperty.name}' is accessed without awaiting.`;
44
45
  (0, utils_1.insertCommentOnce)(member, j, comment);
45
46
  return;
46
47
  }
@@ -121,7 +122,7 @@ function commentOnMatchedReExports(root, j) {
121
122
  specifier.exported.name === 'default')) {
122
123
  if (j.Literal.check(path.value.source)) {
123
124
  const localName = specifier.local.name;
124
- const commentInserted = (0, utils_1.insertCommentOnce)(specifier, j, ` ${utils_1.NEXT_CODEMOD_ERROR_PREFIX} \`${localName}\` export is re-exported. Check if this component uses \`params\` or \`searchParams\``);
125
+ const commentInserted = (0, utils_1.insertCommentOnce)(specifier, j, ` ${utils_2.NEXT_CODEMOD_ERROR_PREFIX} \`${localName}\` export is re-exported. Check if this component uses \`params\` or \`searchParams\``);
125
126
  modified ||= commentInserted;
126
127
  }
127
128
  else if (path.value.source === null) {
@@ -134,7 +135,7 @@ function commentOnMatchedReExports(root, j) {
134
135
  return importPath.value.specifiers.some((importSpecifier) => importSpecifier.local.name === localName);
135
136
  });
136
137
  if (importDeclaration.size() > 0) {
137
- const commentInserted = (0, utils_1.insertCommentOnce)(specifier, j, ` ${utils_1.NEXT_CODEMOD_ERROR_PREFIX} \`${localName}\` export is re-exported. Check if this component uses \`params\` or \`searchParams\``);
138
+ const commentInserted = (0, utils_1.insertCommentOnce)(specifier, j, ` ${utils_2.NEXT_CODEMOD_ERROR_PREFIX} \`${localName}\` export is re-exported. Check if this component uses \`params\` or \`searchParams\``);
138
139
  modified ||= commentInserted;
139
140
  }
140
141
  }
@@ -410,7 +411,7 @@ function transformDynamicProps(source, _api, filePath) {
410
411
  // find the argument `currentParam`
411
412
  const args = callExpression.value.arguments;
412
413
  const propPassedAsArg = args.find((arg) => j.Identifier.check(arg) && arg.name === argName);
413
- const comment = ` ${utils_1.NEXT_CODEMOD_ERROR_PREFIX} '${argName}' is passed as an argument. Any asynchronous properties of 'props' must be awaited when accessed. `;
414
+ const comment = ` ${utils_2.NEXT_CODEMOD_ERROR_PREFIX} '${argName}' is passed as an argument. Any asynchronous properties of 'props' must be awaited when accessed. `;
414
415
  const inserted = (0, utils_1.insertCommentOnce)(propPassedAsArg, j, comment);
415
416
  modified ||= inserted;
416
417
  });
@@ -750,7 +751,7 @@ function commentSpreadProps(path, propsIdentifierName, j) {
750
751
  const objSpreadProperties = functionBodyCollection.find(j.SpreadElement, {
751
752
  argument: { name: propsIdentifierName },
752
753
  });
753
- const comment = ` ${utils_1.NEXT_CODEMOD_ERROR_PREFIX} '${propsIdentifierName}' is used with spread syntax (...). Any asynchronous properties of '${propsIdentifierName}' must be awaited when accessed. `;
754
+ const comment = ` ${utils_2.NEXT_CODEMOD_ERROR_PREFIX} '${propsIdentifierName}' is used with spread syntax (...). Any asynchronous properties of '${propsIdentifierName}' must be awaited when accessed. `;
754
755
  // Add comment before it
755
756
  jsxSpreadProperties.forEach((spread) => {
756
757
  const inserted = (0, utils_1.insertCommentOnce)(spread.value, j, comment);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isReactHookName = exports.TARGET_PROP_NAMES = exports.TARGET_NAMED_EXPORTS = exports.TARGET_ROUTE_EXPORTS = exports.NEXT_CODEMOD_ERROR_PREFIX = exports.NEXTJS_ENTRY_FILES = void 0;
3
+ exports.isReactHookName = exports.TARGET_PROP_NAMES = exports.TARGET_NAMED_EXPORTS = exports.TARGET_ROUTE_EXPORTS = exports.NEXTJS_ENTRY_FILES = void 0;
4
4
  exports.isFunctionType = isFunctionType;
5
5
  exports.isMatchedFunctionExported = isMatchedFunctionExported;
6
6
  exports.determineClientDirective = determineClientDirective;
@@ -19,8 +19,7 @@ exports.containsReactHooksCallExpressions = containsReactHooksCallExpressions;
19
19
  exports.isParentUseCallExpression = isParentUseCallExpression;
20
20
  exports.isParentPromiseAllCallExpression = isParentPromiseAllCallExpression;
21
21
  exports.NEXTJS_ENTRY_FILES = /([\\/]|^)(page|layout|route|default)\.(t|j)sx?$/;
22
- exports.NEXT_CODEMOD_ERROR_PREFIX = '@next-codemod-error';
23
- const NEXT_CODEMOD_IGNORE_ERROR_PREFIX = '@next-codemod-ignore';
22
+ const utils_1 = require("../../../lib/utils");
24
23
  exports.TARGET_ROUTE_EXPORTS = new Set([
25
24
  'GET',
26
25
  'POST',
@@ -332,13 +331,13 @@ function wrapParentheseIfNeeded(hasChainAccess, j, expression) {
332
331
  function existsComment(comments, comment) {
333
332
  const isCodemodErrorComment = comment
334
333
  .trim()
335
- .startsWith(exports.NEXT_CODEMOD_ERROR_PREFIX);
334
+ .startsWith(utils_1.NEXT_CODEMOD_ERROR_PREFIX);
336
335
  let hasIgnoreComment = false;
337
336
  let hasComment = false;
338
337
  if (comments) {
339
338
  comments.forEach((commentNode) => {
340
339
  const currentComment = commentNode.value;
341
- if (currentComment.trim().startsWith(NEXT_CODEMOD_IGNORE_ERROR_PREFIX)) {
340
+ if (currentComment.trim().startsWith(utils_1.NEXT_CODEMOD_IGNORE_ERROR_PREFIX)) {
342
341
  hasIgnoreComment = true;
343
342
  }
344
343
  if (currentComment === comment) {
@@ -4,7 +4,7 @@
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.default = transformer;
6
6
  const parser_1 = require("../lib/parser");
7
- const utils_1 = require("./lib/async-request-api/utils");
7
+ const utils_1 = require("../lib/utils");
8
8
  function transformer(file, _api) {
9
9
  const j = (0, parser_1.createParserFromPath)(file.path);
10
10
  const $j = j(file.source);