@next/codemod 15.0.0-canary.200 → 15.0.0-canary.201

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/upgrade.js CHANGED
@@ -110,6 +110,10 @@ async function runUpgrade(revision, options) {
110
110
  console.log(` - React: v${installedReactVersion}`);
111
111
  console.log(` - Next.js: v${installedNextVersion}`);
112
112
  let shouldStayOnReact18 = false;
113
+ const usesAppDir = isUsingAppDir(process.cwd());
114
+ const usesPagesDir = isUsingPagesDir(process.cwd());
115
+ const isPureAppRouter = usesAppDir && !usesPagesDir;
116
+ const isMixedApp = usesPagesDir && usesAppDir;
113
117
  if (
114
118
  // From release v14.3.0-canary.45, Next.js expects the React version to be 19.0.0-beta.0
115
119
  // If the user is on a version higher than this but is still on React 18, we ask them
@@ -119,11 +123,18 @@ async function runUpgrade(revision, options) {
119
123
  // x-ref(PR): https://github.com/vercel/next.js/pull/65058
120
124
  // x-ref(release): https://github.com/vercel/next.js/releases/tag/v14.3.0-canary.45
121
125
  (0, compare_1.default)(targetNextVersion, '14.3.0-canary.45') >= 0 &&
122
- installedReactVersion.startsWith('18')) {
126
+ installedReactVersion.startsWith('18') &&
127
+ // Pure App Router always uses React 19
128
+ // The mixed case is tricky to handle from a types perspective.
129
+ // We'll recommend to upgrade in the prompt but users can decide to try 18.
130
+ !isPureAppRouter) {
123
131
  const shouldStayOnReact18Res = await (0, prompts_1.default)({
124
132
  type: 'confirm',
125
133
  name: 'shouldStayOnReact18',
126
- message: `Are you using ${picocolors_1.default.underline('only the Pages Router')} (no App Router) and prefer to stay on React 18?`,
134
+ message: `Do you prefer to stay on React 18?` +
135
+ (isMixedApp
136
+ ? " Since you're using both pages/ and app/, we recommend upgrading React to use a consistent version throughout your app."
137
+ : ''),
127
138
  initial: false,
128
139
  active: 'Yes',
129
140
  inactive: 'No',
@@ -296,6 +307,14 @@ function getInstalledReactVersion() {
296
307
  });
297
308
  }
298
309
  }
310
+ function isUsingPagesDir(projectPath) {
311
+ return (fs_1.default.existsSync(path_1.default.resolve(projectPath, 'pages')) ||
312
+ fs_1.default.existsSync(path_1.default.resolve(projectPath, 'src/pages')));
313
+ }
314
+ function isUsingAppDir(projectPath) {
315
+ return (fs_1.default.existsSync(path_1.default.resolve(projectPath, 'app')) ||
316
+ fs_1.default.existsSync(path_1.default.resolve(projectPath, 'src/app')));
317
+ }
299
318
  /*
300
319
  * Heuristics are used to determine whether to Turbopack is enabled or not and
301
320
  * to determine how to update the dev script.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next/codemod",
3
- "version": "15.0.0-canary.200",
3
+ "version": "15.0.0-canary.201",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -155,7 +155,6 @@ function modifyTypes(paramTypeAnnotation, propsIdentifier, root, j) {
155
155
  member.typeAnnotation.typeAnnotation &&
156
156
  j.TSType.check(member.typeAnnotation.typeAnnotation)) {
157
157
  member.typeAnnotation.typeAnnotation = j.tsTypeReference(j.identifier('Promise'), j.tsTypeParameterInstantiation([
158
- // @ts-ignore
159
158
  member.typeAnnotation.typeAnnotation,
160
159
  ]));
161
160
  modified = true;
@@ -203,6 +202,42 @@ function modifyTypes(paramTypeAnnotation, propsIdentifier, root, j) {
203
202
  });
204
203
  }
205
204
  }
205
+ // Deal with type aliases
206
+ if (foundTypes.typeAliases.length > 0) {
207
+ const typeAliasDeclaration = foundTypes.typeAliases[0];
208
+ if (j.TSTypeAliasDeclaration.check(typeAliasDeclaration)) {
209
+ const typeAlias = typeAliasDeclaration.typeAnnotation;
210
+ if (j.TSTypeLiteral.check(typeAlias) &&
211
+ typeAlias.members.length > 0) {
212
+ const typeLiteral = typeAlias;
213
+ typeLiteral.members.forEach((member) => {
214
+ if (j.TSPropertySignature.check(member) &&
215
+ j.Identifier.check(member.key) &&
216
+ utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
217
+ // if it's already a Promise, don't wrap it again, return
218
+ if (member.typeAnnotation &&
219
+ member.typeAnnotation.typeAnnotation &&
220
+ member.typeAnnotation.typeAnnotation.type ===
221
+ 'TSTypeReference' &&
222
+ member.typeAnnotation.typeAnnotation.typeName.type ===
223
+ 'Identifier' &&
224
+ member.typeAnnotation.typeAnnotation.typeName.name ===
225
+ 'Promise') {
226
+ return;
227
+ }
228
+ // Wrap the prop type in Promise<>
229
+ if (member.typeAnnotation &&
230
+ j.TSTypeLiteral.check(member.typeAnnotation.typeAnnotation)) {
231
+ member.typeAnnotation.typeAnnotation = j.tsTypeReference(j.identifier('Promise'), j.tsTypeParameterInstantiation([
232
+ member.typeAnnotation.typeAnnotation,
233
+ ]));
234
+ modified = true;
235
+ }
236
+ }
237
+ });
238
+ }
239
+ }
240
+ }
206
241
  }
207
242
  }
208
243
  propsIdentifier.typeAnnotation = paramTypeAnnotation;
@@ -303,9 +338,7 @@ function transformDynamicProps(source, _api, filePath) {
303
338
  const awaited = awaitMemberAccessOfProp(argName, path, j);
304
339
  modified ||= awaited;
305
340
  }
306
- if (modified) {
307
- modifyTypes(currentParam.typeAnnotation, propsIdentifier, root, j);
308
- }
341
+ modified ||= modifyTypes(currentParam.typeAnnotation, propsIdentifier, root, j);
309
342
  // cases of passing down `props` into any function
310
343
  // Page(props) { callback(props) }
311
344
  // search for all the argument of CallExpression, where currentParam is one of the arguments