@next/codemod 15.0.0-canary.184 → 15.0.0-canary.185
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
|
@@ -6,9 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.runUpgrade = runUpgrade;
|
|
7
7
|
const prompts_1 = __importDefault(require("prompts"));
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const semver_1 = __importDefault(require("semver"));
|
|
10
|
+
const compare_1 = __importDefault(require("semver/functions/compare"));
|
|
9
11
|
const child_process_1 = require("child_process");
|
|
10
12
|
const path_1 = __importDefault(require("path"));
|
|
11
|
-
const compare_versions_1 = require("compare-versions");
|
|
12
13
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
13
14
|
const handle_package_1 = require("../lib/handle-package");
|
|
14
15
|
const transform_1 = require("./transform");
|
|
@@ -49,8 +50,12 @@ async function runUpgrade(revision, options) {
|
|
|
49
50
|
const installedNextVersion = getInstalledNextVersion();
|
|
50
51
|
console.log(`Current Next.js version: v${installedNextVersion}`);
|
|
51
52
|
const targetNextVersion = targetNextPackageJson.version;
|
|
52
|
-
if ((0,
|
|
53
|
-
console.log(`${picocolors_1.default.green('✓')} Current Next.js version is already on
|
|
53
|
+
if ((0, compare_1.default)(installedNextVersion, targetNextVersion) === 0) {
|
|
54
|
+
console.log(`${picocolors_1.default.green('✓')} Current Next.js version is already on the target version "v${targetNextVersion}".`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if ((0, compare_1.default)(installedNextVersion, targetNextVersion) > 0) {
|
|
58
|
+
console.log(`${picocolors_1.default.green('✓')} Current Next.js version is higher than the target version "v${targetNextVersion}".`);
|
|
54
59
|
return;
|
|
55
60
|
}
|
|
56
61
|
const installedReactVersion = getInstalledReactVersion();
|
|
@@ -64,7 +69,7 @@ async function runUpgrade(revision, options) {
|
|
|
64
69
|
// we should only let the user stay on React 18 if they are using pure Pages Router.
|
|
65
70
|
// x-ref(PR): https://github.com/vercel/next.js/pull/65058
|
|
66
71
|
// x-ref(release): https://github.com/vercel/next.js/releases/tag/v14.3.0-canary.45
|
|
67
|
-
(0,
|
|
72
|
+
(0, compare_1.default)(installedNextVersion, '14.3.0-canary.45') >= 0 &&
|
|
68
73
|
installedReactVersion.startsWith('18')) {
|
|
69
74
|
const shouldStayOnReact18Res = await (0, prompts_1.default)({
|
|
70
75
|
type: 'confirm',
|
|
@@ -84,7 +89,7 @@ async function runUpgrade(revision, options) {
|
|
|
84
89
|
const targetReactVersion = shouldStayOnReact18
|
|
85
90
|
? '18.3.1'
|
|
86
91
|
: await loadHighestNPMVersionMatching(`react@${targetNextPackageJson.peerDependencies['react']}`);
|
|
87
|
-
if ((0,
|
|
92
|
+
if ((0, compare_1.default)(targetNextVersion, '15.0.0-canary') >= 0) {
|
|
88
93
|
await suggestTurbopack(appPackageJson);
|
|
89
94
|
}
|
|
90
95
|
const codemods = await suggestCodemods(installedNextVersion, targetNextVersion);
|
|
@@ -94,7 +99,7 @@ async function runUpgrade(revision, options) {
|
|
|
94
99
|
let execCommand = 'npx';
|
|
95
100
|
// The following React codemods are for React 19
|
|
96
101
|
if (!shouldStayOnReact18 &&
|
|
97
|
-
(0,
|
|
102
|
+
(0, compare_1.default)(targetReactVersion, '19.0.0-beta.0') >= 0) {
|
|
98
103
|
shouldRunReactCodemods = await suggestReactCodemods();
|
|
99
104
|
shouldRunReactTypesCodemods = await suggestReactTypesCodemods();
|
|
100
105
|
const execCommandMap = {
|
|
@@ -219,11 +224,25 @@ async function suggestTurbopack(packageJson) {
|
|
|
219
224
|
responseCustomDevScript.customDevScript || devScript;
|
|
220
225
|
}
|
|
221
226
|
async function suggestCodemods(initialNextVersion, targetNextVersion) {
|
|
222
|
-
|
|
227
|
+
// Here we suggest pre-released codemods by their "stable" version.
|
|
228
|
+
// It is because if we suggest by the version range (installed ~ target),
|
|
229
|
+
// pre-released codemods for the target version are not suggested when upgrading.
|
|
230
|
+
// Let's say we have a codemod for v15.0.0-canary.x, and we're upgrading from
|
|
231
|
+
// v15.x -> v15.x. Our initial version is higher than the codemod's version,
|
|
232
|
+
// so the codemod will not be suggested.
|
|
233
|
+
// This is not ideal as the codemods for pre-releases are also targeting the major version.
|
|
234
|
+
// Also, when the user attempts to run the upgrade command twice, and have installed the
|
|
235
|
+
// target version, the behavior must be idempotent and suggest the codemods including the
|
|
236
|
+
// pre-releases of the target version.
|
|
237
|
+
const initial = semver_1.default.parse(initialNextVersion);
|
|
238
|
+
const initialVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.findIndex((versionCodemods) => {
|
|
239
|
+
const codemod = semver_1.default.parse(versionCodemods.version);
|
|
240
|
+
return ((0, compare_1.default)(`${codemod.major}.${codemod.minor}.${codemod.patch}`, `${initial.major}.${initial.minor}.${initial.patch}`) >= 0);
|
|
241
|
+
});
|
|
223
242
|
if (initialVersionIndex === -1) {
|
|
224
243
|
return [];
|
|
225
244
|
}
|
|
226
|
-
let targetVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.findIndex((versionCodemods) => (0,
|
|
245
|
+
let targetVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.findIndex((versionCodemods) => (0, compare_1.default)(versionCodemods.version, targetNextVersion) > 0);
|
|
227
246
|
if (targetVersionIndex === -1) {
|
|
228
247
|
targetVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.length;
|
|
229
248
|
}
|
package/lib/utils.js
CHANGED
|
@@ -39,62 +39,57 @@ exports.TRANSFORMER_INQUIRER_CHOICES = [
|
|
|
39
39
|
{
|
|
40
40
|
title: 'Transform the deprecated automatically injected url property on top level pages to using withRouter',
|
|
41
41
|
value: 'url-to-withrouter',
|
|
42
|
-
version: '6.0',
|
|
42
|
+
version: '6.0.0',
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
title: 'Transforms the withAmp HOC into Next.js 9 page configuration',
|
|
46
46
|
value: 'withamp-to-config',
|
|
47
|
-
version: '8.0',
|
|
47
|
+
version: '8.0.0',
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
title: 'Transforms anonymous components into named components to make sure they work with Fast Refresh',
|
|
51
51
|
value: 'name-default-component',
|
|
52
|
-
version: '9.0',
|
|
52
|
+
version: '9.0.0',
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
title: 'Transforms files that do not import `React` to include the import in order for the new React JSX transform',
|
|
56
56
|
value: 'add-missing-react-import',
|
|
57
|
-
version: '10.0',
|
|
57
|
+
version: '10.0.0',
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
60
|
title: 'Automatically migrates a Create React App project to Next.js (experimental)',
|
|
61
61
|
value: 'cra-to-next',
|
|
62
|
-
version: '11.0',
|
|
62
|
+
version: '11.0.0',
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
title: 'Ensures your <Link> usage is backwards compatible',
|
|
66
66
|
value: 'new-link',
|
|
67
|
-
version: '13.0',
|
|
67
|
+
version: '13.0.0',
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
title: 'Dangerously migrates from `next/legacy/image` to the new `next/image` by adding inline styles and removing unused props (experimental)',
|
|
71
71
|
value: 'next-image-experimental',
|
|
72
|
-
version: '13.0',
|
|
72
|
+
version: '13.0.0',
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
title: 'Safely migrate Next.js 10, 11, 12 applications importing `next/image` to the renamed `next/legacy/image` import in Next.js 13',
|
|
76
76
|
value: 'next-image-to-legacy-image',
|
|
77
|
-
version: '13.0',
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
title: 'Transform App Router Route Segment Config `runtime` value from `experimental-edge` to `edge`',
|
|
81
|
-
value: 'app-dir-runtime-config-experimental-edge',
|
|
82
|
-
version: '13.1.2',
|
|
77
|
+
version: '13.0.0',
|
|
83
78
|
},
|
|
84
79
|
{
|
|
85
80
|
title: 'Uninstall `@next/font` and transform imports to `next/font`',
|
|
86
81
|
value: 'built-in-next-font',
|
|
87
|
-
version: '13.2',
|
|
82
|
+
version: '13.2.0',
|
|
88
83
|
},
|
|
89
84
|
{
|
|
90
85
|
title: 'Migrates certain viewport related metadata from the `metadata` export to a new `viewport` export',
|
|
91
86
|
value: 'metadata-to-viewport-export',
|
|
92
|
-
version: '14.0',
|
|
87
|
+
version: '14.0.0',
|
|
93
88
|
},
|
|
94
89
|
{
|
|
95
90
|
title: 'Transforms imports from `next/server` to `next/og` for usage of Dynamic OG Image Generation',
|
|
96
91
|
value: 'next-og-import',
|
|
97
|
-
version: '14.0',
|
|
92
|
+
version: '14.0.0',
|
|
98
93
|
},
|
|
99
94
|
{
|
|
100
95
|
title: 'Transform `next/dynamic` imports accessing named exports to return an object with a `default` property',
|
|
@@ -111,5 +106,10 @@ exports.TRANSFORMER_INQUIRER_CHOICES = [
|
|
|
111
106
|
value: 'next-async-request-api',
|
|
112
107
|
version: '15.0.0-canary.171',
|
|
113
108
|
},
|
|
109
|
+
{
|
|
110
|
+
title: 'Transform App Router Route Segment Config `runtime` value from `experimental-edge` to `edge`',
|
|
111
|
+
value: 'app-dir-runtime-config-experimental-edge',
|
|
112
|
+
version: '15.0.0-canary.179',
|
|
113
|
+
},
|
|
114
114
|
];
|
|
115
115
|
//# sourceMappingURL=utils.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@next/codemod",
|
|
3
|
-
"version": "15.0.0-canary.
|
|
3
|
+
"version": "15.0.0-canary.185",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"cheerio": "1.0.0-rc.9",
|
|
12
12
|
"commander": "12.1.0",
|
|
13
|
-
"compare-versions": "6.1.1",
|
|
14
13
|
"execa": "4.0.3",
|
|
15
14
|
"find-up": "4.1.0",
|
|
16
15
|
"globby": "11.0.1",
|
|
17
16
|
"is-git-clean": "1.1.0",
|
|
18
17
|
"jscodeshift": "17.0.0",
|
|
19
18
|
"picocolors": "1.0.0",
|
|
20
|
-
"prompts": "2.4.2"
|
|
19
|
+
"prompts": "2.4.2",
|
|
20
|
+
"semver": "7.6.3"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
23
|
"transforms/*.js",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/find-up": "4.0.0",
|
|
38
38
|
"@types/jscodeshift": "0.11.0",
|
|
39
|
-
"@types/prompts": "2.4.2"
|
|
39
|
+
"@types/prompts": "2.4.2",
|
|
40
|
+
"@types/semver": "7.3.1"
|
|
40
41
|
}
|
|
41
42
|
}
|
|
@@ -3,7 +3,7 @@ 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 = `
|
|
6
|
+
const DYNAMIC_IMPORT_WARN_COMMENT = ` @next-codemod-error The APIs under 'next/headers' are async now, need to be manually awaited. `;
|
|
7
7
|
function findDynamicImportsAndComment(root, j) {
|
|
8
8
|
let modified = false;
|
|
9
9
|
// find all the dynamic imports of `next/headers`,
|
|
@@ -136,12 +136,12 @@ function transformDynamicAPI(source, _api, filePath) {
|
|
|
136
136
|
needsReactUseImport = true;
|
|
137
137
|
}
|
|
138
138
|
else {
|
|
139
|
-
const casted = castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes, `
|
|
139
|
+
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 `);
|
|
140
140
|
modified ||= casted;
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
else {
|
|
144
|
-
const casted = castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes,
|
|
144
|
+
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 `);
|
|
145
145
|
modified ||= casted;
|
|
146
146
|
}
|
|
147
147
|
}
|
|
@@ -142,7 +142,7 @@ function commentOnMatchedReExports(root, j) {
|
|
|
142
142
|
specifier.exported.name === 'default')) {
|
|
143
143
|
if (j.Literal.check(path.value.source)) {
|
|
144
144
|
const localName = specifier.local.name;
|
|
145
|
-
const commentInserted = (0, utils_1.insertCommentOnce)(specifier, j, `
|
|
145
|
+
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\``);
|
|
146
146
|
modified ||= commentInserted;
|
|
147
147
|
}
|
|
148
148
|
else if (path.value.source === null) {
|
|
@@ -155,7 +155,7 @@ function commentOnMatchedReExports(root, j) {
|
|
|
155
155
|
return importPath.value.specifiers.some((importSpecifier) => importSpecifier.local.name === localName);
|
|
156
156
|
});
|
|
157
157
|
if (importDeclaration.size() > 0) {
|
|
158
|
-
const commentInserted = (0, utils_1.insertCommentOnce)(specifier, j, `
|
|
158
|
+
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\``);
|
|
159
159
|
modified ||= commentInserted;
|
|
160
160
|
}
|
|
161
161
|
}
|
|
@@ -355,7 +355,7 @@ function transformDynamicProps(source, _api, filePath) {
|
|
|
355
355
|
// find the argument `currentParam`
|
|
356
356
|
const args = callExpression.value.arguments;
|
|
357
357
|
const propPassedAsArg = args.find((arg) => j.Identifier.check(arg) && arg.name === argName);
|
|
358
|
-
const comment = `
|
|
358
|
+
const comment = ` ${utils_1.NEXT_CODEMOD_ERROR_PREFIX} '${argName}' is passed as an argument. Any asynchronous properties of 'props' must be awaited when accessed. `;
|
|
359
359
|
const inserted = (0, utils_1.insertCommentOnce)(propPassedAsArg, j, comment);
|
|
360
360
|
modified ||= inserted;
|
|
361
361
|
});
|
|
@@ -698,7 +698,7 @@ function commentSpreadProps(path, propsIdentifierName, j) {
|
|
|
698
698
|
const objSpreadProperties = functionBodyCollection.find(j.SpreadElement, {
|
|
699
699
|
argument: { name: propsIdentifierName },
|
|
700
700
|
});
|
|
701
|
-
const comment = `
|
|
701
|
+
const comment = ` ${utils_1.NEXT_CODEMOD_ERROR_PREFIX} '${propsIdentifierName}' is used with spread syntax (...). Any asynchronous properties of '${propsIdentifierName}' must be awaited when accessed. `;
|
|
702
702
|
// Add comment before it
|
|
703
703
|
jsxSpreadProperties.forEach((spread) => {
|
|
704
704
|
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.TARGET_PROP_NAMES = exports.TARGET_NAMED_EXPORTS = exports.TARGET_ROUTE_EXPORTS = exports.NEXTJS_ENTRY_FILES = void 0;
|
|
3
|
+
exports.TARGET_PROP_NAMES = exports.TARGET_NAMED_EXPORTS = exports.TARGET_ROUTE_EXPORTS = exports.NEXT_CODEMOD_ERROR_PREFIX = exports.NEXTJS_ENTRY_FILES = void 0;
|
|
4
4
|
exports.isFunctionType = isFunctionType;
|
|
5
5
|
exports.isMatchedFunctionExported = isMatchedFunctionExported;
|
|
6
6
|
exports.determineClientDirective = determineClientDirective;
|
|
@@ -15,6 +15,8 @@ exports.wrapParentheseIfNeeded = wrapParentheseIfNeeded;
|
|
|
15
15
|
exports.insertCommentOnce = insertCommentOnce;
|
|
16
16
|
exports.getVariableDeclaratorId = getVariableDeclaratorId;
|
|
17
17
|
exports.NEXTJS_ENTRY_FILES = /([\\/]|^)(page|layout|route|default)\.(t|j)sx?$/;
|
|
18
|
+
exports.NEXT_CODEMOD_ERROR_PREFIX = '@next-codemod-error';
|
|
19
|
+
const NEXT_CODEMOD_IGNORE_ERROR_PREFIX = '@next-codemod-ignore';
|
|
18
20
|
exports.TARGET_ROUTE_EXPORTS = new Set([
|
|
19
21
|
'GET',
|
|
20
22
|
'POST',
|
|
@@ -321,15 +323,43 @@ function getFunctionPathFromExportPath(exportPath, j, root, namedExportFilter) {
|
|
|
321
323
|
function wrapParentheseIfNeeded(hasChainAccess, j, expression) {
|
|
322
324
|
return hasChainAccess ? j.parenthesizedExpression(expression) : expression;
|
|
323
325
|
}
|
|
324
|
-
function
|
|
325
|
-
|
|
326
|
-
|
|
326
|
+
function existsComment(comments, comment) {
|
|
327
|
+
const isCodemodErrorComment = comment
|
|
328
|
+
.trim()
|
|
329
|
+
.startsWith(exports.NEXT_CODEMOD_ERROR_PREFIX);
|
|
330
|
+
let hasIgnoreComment = false;
|
|
331
|
+
let hasComment = false;
|
|
332
|
+
if (comments) {
|
|
333
|
+
comments.forEach((commentNode) => {
|
|
334
|
+
const currentComment = commentNode.value;
|
|
335
|
+
if (currentComment.trim().startsWith(NEXT_CODEMOD_IGNORE_ERROR_PREFIX)) {
|
|
336
|
+
hasIgnoreComment = true;
|
|
337
|
+
}
|
|
338
|
+
if (currentComment === comment) {
|
|
339
|
+
hasComment = true;
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
// If it's inserting codemod error comment,
|
|
343
|
+
// check if there's already a @next-codemod-ignore comment.
|
|
344
|
+
// if ignore comment exists, bypass the comment insertion.
|
|
345
|
+
if (hasIgnoreComment && isCodemodErrorComment) {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
327
348
|
if (hasComment) {
|
|
328
|
-
return
|
|
349
|
+
return true;
|
|
329
350
|
}
|
|
330
351
|
}
|
|
331
|
-
|
|
332
|
-
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
function insertCommentOnce(node, j, comment) {
|
|
355
|
+
const hasCommentInInlineComments = existsComment(node.comments, comment);
|
|
356
|
+
const hasCommentInLeadingComments = existsComment(node.leadingComments, comment);
|
|
357
|
+
if (!hasCommentInInlineComments && !hasCommentInLeadingComments) {
|
|
358
|
+
// Always insert into inline comment
|
|
359
|
+
node.comments = [j.commentBlock(comment), ...(node.comments || [])];
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
return false;
|
|
333
363
|
}
|
|
334
364
|
function getVariableDeclaratorId(path, j) {
|
|
335
365
|
const parent = path.parentPath;
|