@next/codemod 15.0.0-canary.179 → 15.0.0-canary.181
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/transform.js +10 -4
- package/bin/upgrade.js +33 -49
- package/lib/run-jscodeshift.js +14 -1
- package/lib/utils.js +48 -25
- package/package.json +1 -1
- package/transforms/app-dir-runtime-config-experimental-edge.js +33 -0
- package/transforms/lib/async-request-api/index.js +0 -5
- package/transforms/lib/async-request-api/next-async-dynamic-api.js +3 -3
- package/transforms/lib/async-request-api/next-async-dynamic-prop.js +79 -15
- package/transforms/lib/async-request-api/utils.js +1 -0
- package/lib/codemods.js +0 -108
package/bin/transform.js
CHANGED
|
@@ -36,8 +36,8 @@ async function runTransform(transform, path, options) {
|
|
|
36
36
|
type: 'text',
|
|
37
37
|
name: 'path',
|
|
38
38
|
message: 'On which files or directory should the codemods be applied?',
|
|
39
|
-
|
|
40
|
-
});
|
|
39
|
+
initial: '.',
|
|
40
|
+
}, { onCancel: utils_1.onCancel });
|
|
41
41
|
directory = res.path;
|
|
42
42
|
}
|
|
43
43
|
if (!transform) {
|
|
@@ -45,8 +45,14 @@ async function runTransform(transform, path, options) {
|
|
|
45
45
|
type: 'select',
|
|
46
46
|
name: 'transformer',
|
|
47
47
|
message: 'Which transform would you like to apply?',
|
|
48
|
-
choices: utils_1.TRANSFORMER_INQUIRER_CHOICES,
|
|
49
|
-
|
|
48
|
+
choices: utils_1.TRANSFORMER_INQUIRER_CHOICES.reverse().map(({ title, value, version }) => {
|
|
49
|
+
return {
|
|
50
|
+
title: `(v${version}) ${value}`,
|
|
51
|
+
description: title,
|
|
52
|
+
value,
|
|
53
|
+
};
|
|
54
|
+
}),
|
|
55
|
+
}, { onCancel: utils_1.onCancel });
|
|
50
56
|
transformer = res.transformer;
|
|
51
57
|
}
|
|
52
58
|
const filesExpanded = expandFilePathsIfNeeded([directory]);
|
package/bin/upgrade.js
CHANGED
|
@@ -10,9 +10,9 @@ const child_process_1 = require("child_process");
|
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
11
|
const compare_versions_1 = require("compare-versions");
|
|
12
12
|
const chalk_1 = __importDefault(require("chalk"));
|
|
13
|
-
const codemods_1 = require("../lib/codemods");
|
|
14
13
|
const handle_package_1 = require("../lib/handle-package");
|
|
15
14
|
const transform_1 = require("./transform");
|
|
15
|
+
const utils_1 = require("../lib/utils");
|
|
16
16
|
/**
|
|
17
17
|
* @param query
|
|
18
18
|
* @example loadHighestNPMVersionMatching("react@^18.3.0 || ^19.0.0") === Promise<"19.0.0">
|
|
@@ -29,16 +29,19 @@ async function runUpgrade(revision, options) {
|
|
|
29
29
|
const { verbose } = options;
|
|
30
30
|
const appPackageJsonPath = path_1.default.resolve(process.cwd(), 'package.json');
|
|
31
31
|
let appPackageJson = JSON.parse(fs_1.default.readFileSync(appPackageJsonPath, 'utf8'));
|
|
32
|
-
await detectWorkspace(appPackageJson);
|
|
33
32
|
let targetNextPackageJson;
|
|
34
33
|
const res = await fetch(`https://registry.npmjs.org/next/${revision}`);
|
|
35
34
|
if (res.status === 200) {
|
|
36
35
|
targetNextPackageJson = await res.json();
|
|
37
36
|
}
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const validRevision = targetNextPackageJson !== null &&
|
|
38
|
+
typeof targetNextPackageJson === 'object' &&
|
|
39
|
+
'version' in targetNextPackageJson &&
|
|
40
|
+
'peerDependencies' in targetNextPackageJson;
|
|
41
|
+
if (!validRevision) {
|
|
42
|
+
throw new Error(`${chalk_1.default.yellow(`next@${revision}`)} does not exist. Make sure you entered a valid Next.js version or dist-tag. Check available versions at ${chalk_1.default.underline('https://www.npmjs.com/package/next?activeTab=versions')}.`);
|
|
40
43
|
}
|
|
41
|
-
const installedNextVersion =
|
|
44
|
+
const installedNextVersion = getInstalledNextVersion();
|
|
42
45
|
const targetNextVersion = targetNextPackageJson.version;
|
|
43
46
|
// We're resolving a specific version here to avoid including "ugly" version queries
|
|
44
47
|
// in the manifest.
|
|
@@ -81,28 +84,17 @@ async function runUpgrade(revision, options) {
|
|
|
81
84
|
}
|
|
82
85
|
console.log(`\n${chalk_1.default.green('✔')} Your Next.js project has been upgraded successfully. ${chalk_1.default.bold('Time to ship! 🚢')}`);
|
|
83
86
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
initial: false,
|
|
95
|
-
}, { onCancel: () => process.exit(0) });
|
|
96
|
-
if (!response.value) {
|
|
97
|
-
process.exit(0);
|
|
87
|
+
function getInstalledNextVersion() {
|
|
88
|
+
try {
|
|
89
|
+
return require(require.resolve('next/package.json', {
|
|
90
|
+
paths: [process.cwd()],
|
|
91
|
+
})).version;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
throw new Error(`Failed to get the installed Next.js version at "${process.cwd()}".\nIf you're using a monorepo, please run this command from the Next.js app directory.`, {
|
|
95
|
+
cause: error,
|
|
96
|
+
});
|
|
98
97
|
}
|
|
99
|
-
}
|
|
100
|
-
async function getInstalledNextVersion() {
|
|
101
|
-
const installedNextPackageJsonDir = require.resolve('next/package.json', {
|
|
102
|
-
paths: [process.cwd()],
|
|
103
|
-
});
|
|
104
|
-
const installedNextPackageJson = JSON.parse(fs_1.default.readFileSync(installedNextPackageJsonDir, 'utf8'));
|
|
105
|
-
return installedNextPackageJson.version;
|
|
106
98
|
}
|
|
107
99
|
/*
|
|
108
100
|
* Heuristics are used to determine whether to Turbopack is enabled or not and
|
|
@@ -122,13 +114,9 @@ async function suggestTurbopack(packageJson) {
|
|
|
122
114
|
const responseTurbopack = await (0, prompts_1.default)({
|
|
123
115
|
type: 'confirm',
|
|
124
116
|
name: 'enable',
|
|
125
|
-
message: 'Turbopack
|
|
117
|
+
message: 'Enable Turbopack for next dev?',
|
|
126
118
|
initial: true,
|
|
127
|
-
}, {
|
|
128
|
-
onCancel: () => {
|
|
129
|
-
process.exit(0);
|
|
130
|
-
},
|
|
131
|
-
});
|
|
119
|
+
}, { onCancel: utils_1.onCancel });
|
|
132
120
|
if (!responseTurbopack.enable) {
|
|
133
121
|
return;
|
|
134
122
|
}
|
|
@@ -136,46 +124,42 @@ async function suggestTurbopack(packageJson) {
|
|
|
136
124
|
packageJson.scripts['dev'] = devScript.replace('next dev', 'next dev --turbo');
|
|
137
125
|
return;
|
|
138
126
|
}
|
|
127
|
+
console.log(`${chalk_1.default.yellow('⚠')} Could not find "${chalk_1.default.bold('next dev')}" in your dev script.`);
|
|
139
128
|
const responseCustomDevScript = await (0, prompts_1.default)({
|
|
140
129
|
type: 'text',
|
|
141
130
|
name: 'customDevScript',
|
|
142
|
-
message: 'Please add
|
|
131
|
+
message: 'Please manually add "--turbo" to your dev command.',
|
|
143
132
|
initial: devScript,
|
|
144
|
-
});
|
|
133
|
+
}, { onCancel: utils_1.onCancel });
|
|
145
134
|
packageJson.scripts['dev'] =
|
|
146
135
|
responseCustomDevScript.customDevScript || devScript;
|
|
147
136
|
}
|
|
148
137
|
async function suggestCodemods(initialNextVersion, targetNextVersion) {
|
|
149
|
-
const initialVersionIndex =
|
|
138
|
+
const initialVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.findIndex((versionCodemods) => (0, compare_versions_1.compareVersions)(versionCodemods.version, initialNextVersion) > 0);
|
|
150
139
|
if (initialVersionIndex === -1) {
|
|
151
140
|
return [];
|
|
152
141
|
}
|
|
153
|
-
let targetVersionIndex =
|
|
142
|
+
let targetVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.findIndex((versionCodemods) => (0, compare_versions_1.compareVersions)(versionCodemods.version, targetNextVersion) > 0);
|
|
154
143
|
if (targetVersionIndex === -1) {
|
|
155
|
-
targetVersionIndex =
|
|
144
|
+
targetVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.length;
|
|
156
145
|
}
|
|
157
|
-
const relevantCodemods =
|
|
158
|
-
.slice(initialVersionIndex, targetVersionIndex)
|
|
159
|
-
.flatMap((versionCodemods) => versionCodemods.codemods);
|
|
146
|
+
const relevantCodemods = utils_1.TRANSFORMER_INQUIRER_CHOICES.slice(initialVersionIndex, targetVersionIndex);
|
|
160
147
|
if (relevantCodemods.length === 0) {
|
|
161
148
|
return [];
|
|
162
149
|
}
|
|
163
150
|
const { codemods } = await (0, prompts_1.default)({
|
|
164
151
|
type: 'multiselect',
|
|
165
152
|
name: 'codemods',
|
|
166
|
-
message:
|
|
167
|
-
choices: relevantCodemods.map((
|
|
153
|
+
message: `The following ${chalk_1.default.blue('codemods')} are recommended for your upgrade. Select the ones to apply.`,
|
|
154
|
+
choices: relevantCodemods.reverse().map(({ title, value, version }) => {
|
|
168
155
|
return {
|
|
169
|
-
title:
|
|
170
|
-
|
|
156
|
+
title: `(v${version}) ${value}`,
|
|
157
|
+
description: title,
|
|
158
|
+
value,
|
|
171
159
|
selected: true,
|
|
172
160
|
};
|
|
173
161
|
}),
|
|
174
|
-
}, {
|
|
175
|
-
onCancel: () => {
|
|
176
|
-
process.exit(0);
|
|
177
|
-
},
|
|
178
|
-
});
|
|
162
|
+
}, { onCancel: utils_1.onCancel });
|
|
179
163
|
return codemods;
|
|
180
164
|
}
|
|
181
165
|
//# sourceMappingURL=upgrade.js.map
|
package/lib/run-jscodeshift.js
CHANGED
|
@@ -10,7 +10,20 @@ function runJscodeshift(transformerPath, flags, files) {
|
|
|
10
10
|
// we run jscodeshift in the same process to be able to
|
|
11
11
|
// share state between the main CRA transform and sub-transforms
|
|
12
12
|
return Runner_1.default.run(transformerPath, files, {
|
|
13
|
-
ignorePattern: [
|
|
13
|
+
ignorePattern: [
|
|
14
|
+
'**/node_modules/**',
|
|
15
|
+
'**/.next/**',
|
|
16
|
+
'**/build/**',
|
|
17
|
+
// type files
|
|
18
|
+
'**/*.d.ts',
|
|
19
|
+
'**/*.d.cts',
|
|
20
|
+
'**/*.d.mts',
|
|
21
|
+
// test files
|
|
22
|
+
'**/*.test.*',
|
|
23
|
+
'**/*.spec.*',
|
|
24
|
+
'**/__tests__/**',
|
|
25
|
+
'**/__mocks__/**',
|
|
26
|
+
],
|
|
14
27
|
extensions: 'tsx,ts,jsx,js',
|
|
15
28
|
parser: 'tsx',
|
|
16
29
|
verbose: 2,
|
package/lib/utils.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.TRANSFORMER_INQUIRER_CHOICES = void 0;
|
|
7
7
|
exports.checkGitStatus = checkGitStatus;
|
|
8
|
+
exports.onCancel = onCancel;
|
|
8
9
|
const picocolors_1 = require("picocolors");
|
|
9
10
|
const is_git_clean_1 = __importDefault(require("is-git-clean"));
|
|
10
11
|
function checkGitStatus(force) {
|
|
@@ -31,62 +32,84 @@ function checkGitStatus(force) {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
}
|
|
35
|
+
function onCancel() {
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
34
38
|
exports.TRANSFORMER_INQUIRER_CHOICES = [
|
|
35
39
|
{
|
|
36
|
-
title: '
|
|
37
|
-
value: '
|
|
40
|
+
title: 'Transform the deprecated automatically injected url property on top level pages to using withRouter',
|
|
41
|
+
value: 'url-to-withrouter',
|
|
42
|
+
version: '6.0',
|
|
38
43
|
},
|
|
39
44
|
{
|
|
40
|
-
title: '
|
|
41
|
-
value: '
|
|
45
|
+
title: 'Transforms the withAmp HOC into Next.js 9 page configuration',
|
|
46
|
+
value: 'withamp-to-config',
|
|
47
|
+
version: '8.0',
|
|
42
48
|
},
|
|
43
49
|
{
|
|
44
|
-
title: '
|
|
45
|
-
value: '
|
|
50
|
+
title: 'Transforms anonymous components into named components to make sure they work with Fast Refresh',
|
|
51
|
+
value: 'name-default-component',
|
|
52
|
+
version: '9.0',
|
|
46
53
|
},
|
|
47
54
|
{
|
|
48
|
-
title: '
|
|
49
|
-
value: '
|
|
55
|
+
title: 'Transforms files that do not import `React` to include the import in order for the new React JSX transform',
|
|
56
|
+
value: 'add-missing-react-import',
|
|
57
|
+
version: '10.0',
|
|
50
58
|
},
|
|
51
59
|
{
|
|
52
|
-
title: '
|
|
60
|
+
title: 'Automatically migrates a Create React App project to Next.js (experimental)',
|
|
53
61
|
value: 'cra-to-next',
|
|
62
|
+
version: '11.0',
|
|
54
63
|
},
|
|
55
64
|
{
|
|
56
|
-
title: '
|
|
65
|
+
title: 'Ensures your <Link> usage is backwards compatible',
|
|
57
66
|
value: 'new-link',
|
|
67
|
+
version: '13.0',
|
|
58
68
|
},
|
|
59
69
|
{
|
|
60
|
-
title: '
|
|
61
|
-
value: 'next-
|
|
70
|
+
title: 'Dangerously migrates from `next/legacy/image` to the new `next/image` by adding inline styles and removing unused props (experimental)',
|
|
71
|
+
value: 'next-image-experimental',
|
|
72
|
+
version: '13.0',
|
|
62
73
|
},
|
|
63
74
|
{
|
|
64
|
-
title: '
|
|
65
|
-
value: '
|
|
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
|
+
value: 'next-image-to-legacy-image',
|
|
77
|
+
version: '13.0',
|
|
66
78
|
},
|
|
67
79
|
{
|
|
68
|
-
title: 'next
|
|
69
|
-
value: '
|
|
80
|
+
title: 'Uninstall `@next/font` and transform imports to `next/font`',
|
|
81
|
+
value: 'built-in-next-font',
|
|
82
|
+
version: '13.2',
|
|
70
83
|
},
|
|
71
84
|
{
|
|
72
|
-
title: '
|
|
73
|
-
value: '
|
|
85
|
+
title: 'Migrates certain viewport related metadata from the `metadata` export to a new `viewport` export',
|
|
86
|
+
value: 'metadata-to-viewport-export',
|
|
87
|
+
version: '14.0',
|
|
74
88
|
},
|
|
75
89
|
{
|
|
76
|
-
title: '
|
|
77
|
-
value: 'next-
|
|
90
|
+
title: 'Transforms imports from `next/server` to `next/og` for usage of Dynamic OG Image Generation',
|
|
91
|
+
value: 'next-og-import',
|
|
92
|
+
version: '14.0',
|
|
78
93
|
},
|
|
79
94
|
{
|
|
80
|
-
title: '
|
|
81
|
-
value: '
|
|
95
|
+
title: 'Transforms dynamic imports that return the named export itself to a module like object',
|
|
96
|
+
value: 'next-dynamic-access-named-export',
|
|
97
|
+
version: '15.0.0-canary.44',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
title: 'Install `@vercel/functions` to replace `geo` and `ip` properties on `NextRequest`',
|
|
101
|
+
value: 'next-request-geo-ip',
|
|
102
|
+
version: '15.0.0-canary.153',
|
|
82
103
|
},
|
|
83
104
|
{
|
|
84
|
-
title: '
|
|
105
|
+
title: 'Transforms usage of Next.js async Request APIs',
|
|
85
106
|
value: 'next-async-request-api',
|
|
107
|
+
version: '15.0.0-canary.171',
|
|
86
108
|
},
|
|
87
109
|
{
|
|
88
|
-
title: '
|
|
89
|
-
value: '
|
|
110
|
+
title: 'Transforms `experimental-edge` to `edge` in the `runtime` route segment configuration within the App Router',
|
|
111
|
+
value: 'app-dir-runtime-config-experimental-edge',
|
|
112
|
+
version: '15.0.0-canary.179',
|
|
90
113
|
},
|
|
91
114
|
];
|
|
92
115
|
//# sourceMappingURL=utils.js.map
|
package/package.json
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = transformer;
|
|
4
|
+
function transformer(file, api) {
|
|
5
|
+
if (process.env.NODE_ENV !== 'test' &&
|
|
6
|
+
!/[/\\]app[/\\].*?(page|layout|route)\.[^/\\]+$/.test(file.path)) {
|
|
7
|
+
return file.source;
|
|
8
|
+
}
|
|
9
|
+
const j = api.jscodeshift.withParser('tsx');
|
|
10
|
+
const root = j(file.source);
|
|
11
|
+
const runtimeExport = root.find(j.ExportNamedDeclaration, {
|
|
12
|
+
declaration: {
|
|
13
|
+
type: 'VariableDeclaration',
|
|
14
|
+
declarations: [
|
|
15
|
+
{
|
|
16
|
+
id: { name: 'runtime' },
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
if (runtimeExport.size() !== 1) {
|
|
22
|
+
return file.source;
|
|
23
|
+
}
|
|
24
|
+
const runtimeValue = runtimeExport.find(j.StringLiteral, {
|
|
25
|
+
value: 'experimental-edge',
|
|
26
|
+
});
|
|
27
|
+
if (runtimeValue.size() !== 1) {
|
|
28
|
+
return file.source;
|
|
29
|
+
}
|
|
30
|
+
runtimeValue.replaceWith(j.stringLiteral('edge'));
|
|
31
|
+
return root.toSource();
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=app-dir-runtime-config-experimental-edge.js.map
|
|
@@ -4,11 +4,6 @@ exports.default = transform;
|
|
|
4
4
|
const next_async_dynamic_prop_1 = require("./next-async-dynamic-prop");
|
|
5
5
|
const next_async_dynamic_api_1 = require("./next-async-dynamic-api");
|
|
6
6
|
function transform(file, api) {
|
|
7
|
-
const filePath = file.path;
|
|
8
|
-
// if it's node_modules or types file, skip
|
|
9
|
-
if (/node_modules/.test(filePath) || /\.d\.(m|c)?ts$/.test(filePath)) {
|
|
10
|
-
return file.source;
|
|
11
|
-
}
|
|
12
7
|
const transforms = [next_async_dynamic_prop_1.transformDynamicProps, next_async_dynamic_api_1.transformDynamicAPI];
|
|
13
8
|
return transforms.reduce((source, transformFn) => {
|
|
14
9
|
const result = transformFn(source, api, file.path);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.transformDynamicAPI = transformDynamicAPI;
|
|
4
4
|
const utils_1 = require("./utils");
|
|
5
|
-
const DYNAMIC_IMPORT_WARN_COMMENT = ` The APIs under 'next/headers' are async now, need to be manually awaited. `;
|
|
5
|
+
const DYNAMIC_IMPORT_WARN_COMMENT = ` Next.js Dynamic Async API Codemod: The APIs under 'next/headers' are async now, need to be manually awaited. `;
|
|
6
6
|
function findDynamicImportsAndComment(root, j) {
|
|
7
7
|
let modified = false;
|
|
8
8
|
// find all the dynamic imports of `next/headers`,
|
|
@@ -134,11 +134,11 @@ function transformDynamicAPI(source, api, filePath) {
|
|
|
134
134
|
needsReactUseImport = true;
|
|
135
135
|
}
|
|
136
136
|
else {
|
|
137
|
-
castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes, `
|
|
137
|
+
castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes, ` Next.js Dynamic Async API Codemod: Manually await this call, if it's a Server Component `);
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
else {
|
|
141
|
-
castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes, '
|
|
141
|
+
castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes, ' Next.js Dynamic Async API Codemod: please manually await this call, codemod cannot transform due to undetermined async scope ');
|
|
142
142
|
}
|
|
143
143
|
modified = true;
|
|
144
144
|
}
|
|
@@ -23,6 +23,9 @@ function awaitMemberAccessOfProp(propIdName, path, j) {
|
|
|
23
23
|
// await each member access
|
|
24
24
|
memberAccess.forEach((memberAccessPath) => {
|
|
25
25
|
const member = memberAccessPath.value;
|
|
26
|
+
if (isParentPromiseAllCallExpression(memberAccessPath, j)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
26
29
|
// check if it's already awaited
|
|
27
30
|
if (memberAccessPath.parentPath?.value.type === 'AwaitExpression') {
|
|
28
31
|
return;
|
|
@@ -43,6 +46,33 @@ function awaitMemberAccessOfProp(propIdName, path, j) {
|
|
|
43
46
|
}
|
|
44
47
|
return hasAwaited;
|
|
45
48
|
}
|
|
49
|
+
function isParentUseCallExpression(path, j) {
|
|
50
|
+
if (
|
|
51
|
+
// member access parentPath is argument
|
|
52
|
+
j.CallExpression.check(path.parent.value) &&
|
|
53
|
+
// member access is first argument
|
|
54
|
+
path.parent.value.arguments[0] === path.value &&
|
|
55
|
+
// function name is `use`
|
|
56
|
+
j.Identifier.check(path.parent.value.callee) &&
|
|
57
|
+
path.parent.value.callee.name === 'use') {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
function isParentPromiseAllCallExpression(path, j) {
|
|
63
|
+
const argsParent = path.parent;
|
|
64
|
+
const callParent = argsParent?.parent;
|
|
65
|
+
if (j.ArrayExpression.check(argsParent.value) &&
|
|
66
|
+
j.CallExpression.check(callParent.value) &&
|
|
67
|
+
j.MemberExpression.check(callParent.value.callee) &&
|
|
68
|
+
j.Identifier.check(callParent.value.callee.object) &&
|
|
69
|
+
callParent.value.callee.object.name === 'Promise' &&
|
|
70
|
+
j.Identifier.check(callParent.value.callee.property) &&
|
|
71
|
+
callParent.value.callee.property.name === 'all') {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
46
76
|
function applyUseAndRenameAccessedProp(propIdName, path, j) {
|
|
47
77
|
// search the member access of the prop, and rename the member access to the member value
|
|
48
78
|
// e.g.
|
|
@@ -60,6 +90,10 @@ function applyUseAndRenameAccessedProp(propIdName, path, j) {
|
|
|
60
90
|
const accessedNames = [];
|
|
61
91
|
// rename each member access
|
|
62
92
|
memberAccess.forEach((memberAccessPath) => {
|
|
93
|
+
// If the member access expression is first argument of `use()`, we skip
|
|
94
|
+
if (isParentUseCallExpression(memberAccessPath, j)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
63
97
|
const member = memberAccessPath.value;
|
|
64
98
|
const memberProperty = member.property;
|
|
65
99
|
if (j.Identifier.check(memberProperty)) {
|
|
@@ -89,7 +123,7 @@ function applyUseAndRenameAccessedProp(propIdName, path, j) {
|
|
|
89
123
|
}
|
|
90
124
|
return modified;
|
|
91
125
|
}
|
|
92
|
-
const MATCHED_FILE_PATTERNS = /([\\/]|^)(page|layout)\.(t|j)sx?$/;
|
|
126
|
+
const MATCHED_FILE_PATTERNS = /([\\/]|^)(page|layout|route)\.(t|j)sx?$/;
|
|
93
127
|
function modifyTypes(paramTypeAnnotation, propsIdentifier, root, j) {
|
|
94
128
|
if (paramTypeAnnotation && paramTypeAnnotation.typeAnnotation) {
|
|
95
129
|
const typeAnnotation = paramTypeAnnotation.typeAnnotation;
|
|
@@ -233,17 +267,13 @@ function transformDynamicProps(source, api, filePath) {
|
|
|
233
267
|
currentParam.properties.forEach((prop) => {
|
|
234
268
|
if (
|
|
235
269
|
// Could be `Property` or `ObjectProperty`
|
|
236
|
-
|
|
237
|
-
prop.key
|
|
270
|
+
(j.Property.check(prop) || j.ObjectProperty.check(prop)) &&
|
|
271
|
+
j.Identifier.check(prop.key) &&
|
|
238
272
|
utils_1.TARGET_PROP_NAMES.has(prop.key.name)) {
|
|
239
273
|
const value = 'value' in prop ? prop.value : null;
|
|
240
274
|
propertiesMap.set(prop.key.name, value);
|
|
241
275
|
}
|
|
242
276
|
});
|
|
243
|
-
modifyTypes(currentParam.typeAnnotation, propsIdentifier, root, j);
|
|
244
|
-
// Override the first param to `props`
|
|
245
|
-
params[propsArgumentIndex] = propsIdentifier;
|
|
246
|
-
modified = true;
|
|
247
277
|
modifiedPropArgument = true;
|
|
248
278
|
}
|
|
249
279
|
else if (currentParam.type === 'Identifier') {
|
|
@@ -278,7 +308,7 @@ function transformDynamicProps(source, api, filePath) {
|
|
|
278
308
|
// find the argument `currentParam`
|
|
279
309
|
const args = callExpression.value.arguments;
|
|
280
310
|
const propPassedAsArg = args.find((arg) => j.Identifier.check(arg) && arg.name === argName);
|
|
281
|
-
const comment = ` '${argName}' is passed as an argument. Any asynchronous properties of 'props' must be awaited when accessed. `;
|
|
311
|
+
const comment = ` Next.js Dynamic Async API Codemod: '${argName}' is passed as an argument. Any asynchronous properties of 'props' must be awaited when accessed. `;
|
|
282
312
|
(0, utils_1.insertCommentOnce)(propPassedAsArg, j, comment);
|
|
283
313
|
modified = true;
|
|
284
314
|
});
|
|
@@ -287,7 +317,16 @@ function transformDynamicProps(source, api, filePath) {
|
|
|
287
317
|
}
|
|
288
318
|
}
|
|
289
319
|
if (modifiedPropArgument) {
|
|
290
|
-
resolveAsyncProp(path, propertiesMap, propsIdentifier.name, allProperties, isDefaultExport);
|
|
320
|
+
const isModified = resolveAsyncProp(path, propertiesMap, propsIdentifier.name, allProperties, isDefaultExport);
|
|
321
|
+
if (isModified) {
|
|
322
|
+
// Make TS happy
|
|
323
|
+
if (j.ObjectPattern.check(currentParam)) {
|
|
324
|
+
modifyTypes(currentParam.typeAnnotation, propsIdentifier, root, j);
|
|
325
|
+
}
|
|
326
|
+
// Override the first param to `props`
|
|
327
|
+
params[propsArgumentIndex] = propsIdentifier;
|
|
328
|
+
modified = true;
|
|
329
|
+
}
|
|
291
330
|
}
|
|
292
331
|
}
|
|
293
332
|
// Helper function to insert `const params = await asyncParams;` at the beginning of the function body
|
|
@@ -385,22 +424,46 @@ function transformDynamicProps(source, api, filePath) {
|
|
|
385
424
|
functionBody.unshift(destructionOtherPropertiesDeclaration);
|
|
386
425
|
}
|
|
387
426
|
}
|
|
427
|
+
let modifiedPropertyCount = 0;
|
|
388
428
|
for (const [matchedPropName, paramsProperty] of propertiesMap) {
|
|
389
429
|
if (!utils_1.TARGET_PROP_NAMES.has(matchedPropName)) {
|
|
390
430
|
continue;
|
|
391
431
|
}
|
|
432
|
+
// In client component, if the param is already wrapped with `use()`, skip the transformation
|
|
433
|
+
if (isClientComponent) {
|
|
434
|
+
let shouldSkip = false;
|
|
435
|
+
const propPaths = j(path).find(j.Identifier, {
|
|
436
|
+
name: matchedPropName,
|
|
437
|
+
});
|
|
438
|
+
for (const propPath of propPaths.paths()) {
|
|
439
|
+
if (isParentUseCallExpression(propPath, j)) {
|
|
440
|
+
// Skip transformation
|
|
441
|
+
shouldSkip = true;
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (shouldSkip) {
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
392
449
|
const propRenamedId = j.Identifier.check(paramsProperty)
|
|
393
450
|
? paramsProperty.name
|
|
394
451
|
: null;
|
|
395
452
|
const propName = propRenamedId || matchedPropName;
|
|
396
453
|
// if propName is not used in lower scope, and it stars with unused prefix `_`,
|
|
397
454
|
// also skip the transformation
|
|
398
|
-
const
|
|
399
|
-
|
|
455
|
+
const functionBodyPath = path.get('body');
|
|
456
|
+
const hasUsedInBody = j(functionBodyPath)
|
|
457
|
+
.find(j.Identifier, {
|
|
458
|
+
name: propName,
|
|
459
|
+
})
|
|
460
|
+
.size() > 0;
|
|
461
|
+
if (!hasUsedInBody && propName.startsWith('_'))
|
|
400
462
|
continue;
|
|
463
|
+
modifiedPropertyCount++;
|
|
401
464
|
const propNameIdentifier = j.identifier(matchedPropName);
|
|
402
465
|
const propsIdentifier = j.identifier(propsIdentifierName);
|
|
403
|
-
const
|
|
466
|
+
const accessedPropIdExpr = j.memberExpression(propsIdentifier, propNameIdentifier);
|
|
404
467
|
// Check param property value, if it's destructed, we need to destruct it as well
|
|
405
468
|
// e.g.
|
|
406
469
|
// input: Page({ params: { slug } })
|
|
@@ -427,7 +490,7 @@ function transformDynamicProps(source, api, filePath) {
|
|
|
427
490
|
if (isAsyncFunc) {
|
|
428
491
|
// If it's async function, add await to the async props.<propName>
|
|
429
492
|
const paramAssignment = j.variableDeclaration('const', [
|
|
430
|
-
j.variableDeclarator(j.identifier(propName), j.awaitExpression(
|
|
493
|
+
j.variableDeclarator(j.identifier(propName), j.awaitExpression(accessedPropIdExpr)),
|
|
431
494
|
]);
|
|
432
495
|
if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
|
|
433
496
|
functionBody.unshift(paramAssignment);
|
|
@@ -445,7 +508,7 @@ function transformDynamicProps(source, api, filePath) {
|
|
|
445
508
|
(0, utils_1.turnFunctionReturnTypeToAsync)(node, j);
|
|
446
509
|
// Insert `const <propName> = await props.<propName>;` at the beginning of the function body
|
|
447
510
|
const paramAssignment = j.variableDeclaration('const', [
|
|
448
|
-
j.variableDeclarator(j.identifier(propName), j.awaitExpression(
|
|
511
|
+
j.variableDeclarator(j.identifier(propName), j.awaitExpression(accessedPropIdExpr)),
|
|
449
512
|
]);
|
|
450
513
|
if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
|
|
451
514
|
functionBody.unshift(paramAssignment);
|
|
@@ -455,7 +518,7 @@ function transformDynamicProps(source, api, filePath) {
|
|
|
455
518
|
}
|
|
456
519
|
else {
|
|
457
520
|
const paramAssignment = j.variableDeclaration('const', [
|
|
458
|
-
j.variableDeclarator(j.identifier(propName), j.callExpression(j.identifier('use'), [
|
|
521
|
+
j.variableDeclarator(j.identifier(propName), j.callExpression(j.identifier('use'), [accessedPropIdExpr])),
|
|
459
522
|
]);
|
|
460
523
|
if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
|
|
461
524
|
needsReactUseImport = true;
|
|
@@ -465,6 +528,7 @@ function transformDynamicProps(source, api, filePath) {
|
|
|
465
528
|
}
|
|
466
529
|
}
|
|
467
530
|
}
|
|
531
|
+
return modifiedPropertyCount > 0;
|
|
468
532
|
}
|
|
469
533
|
const defaultExportsDeclarations = root.find(j.ExportDefaultDeclaration);
|
|
470
534
|
defaultExportsDeclarations.forEach((path) => {
|
|
@@ -26,6 +26,7 @@ exports.TARGET_ROUTE_EXPORTS = new Set([
|
|
|
26
26
|
exports.TARGET_NAMED_EXPORTS = new Set([
|
|
27
27
|
// For page and layout
|
|
28
28
|
'generateMetadata',
|
|
29
|
+
'generateViewport',
|
|
29
30
|
...exports.TARGET_ROUTE_EXPORTS,
|
|
30
31
|
]);
|
|
31
32
|
exports.TARGET_PROP_NAMES = new Set(['params', 'searchParams']);
|
package/lib/codemods.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.availableCodemods = void 0;
|
|
4
|
-
exports.availableCodemods = [
|
|
5
|
-
{
|
|
6
|
-
version: '6',
|
|
7
|
-
codemods: [
|
|
8
|
-
{
|
|
9
|
-
title: 'Use withRouter',
|
|
10
|
-
value: 'url-to-withrouter',
|
|
11
|
-
},
|
|
12
|
-
],
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
version: '8',
|
|
16
|
-
codemods: [
|
|
17
|
-
{
|
|
18
|
-
title: 'Transform AMP HOC into page config',
|
|
19
|
-
value: 'withamp-to-config',
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
version: '9',
|
|
25
|
-
codemods: [
|
|
26
|
-
{
|
|
27
|
-
title: 'Transform Anonymous Components into Named Components',
|
|
28
|
-
value: 'name-default-component',
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
version: '10',
|
|
34
|
-
codemods: [
|
|
35
|
-
{
|
|
36
|
-
title: 'Add React Import',
|
|
37
|
-
value: 'add-missing-react-import',
|
|
38
|
-
},
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
version: '11',
|
|
43
|
-
codemods: [
|
|
44
|
-
{
|
|
45
|
-
title: 'Migrate from CRA',
|
|
46
|
-
value: 'cra-to-next',
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
version: '13.0',
|
|
52
|
-
codemods: [
|
|
53
|
-
{
|
|
54
|
-
title: 'Remove <a> Tags From Link Components',
|
|
55
|
-
value: 'new-link',
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
title: 'Migrate to the New Image Component',
|
|
59
|
-
value: 'next-image-experimental',
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
title: 'Rename Next Image Imports',
|
|
63
|
-
value: 'next-image-to-legacy-image',
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
version: '13.2',
|
|
69
|
-
codemods: [
|
|
70
|
-
{
|
|
71
|
-
title: 'Use Built-in Font',
|
|
72
|
-
value: 'built-in-next-font',
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
version: '14.0',
|
|
78
|
-
codemods: [
|
|
79
|
-
{
|
|
80
|
-
title: 'Migrate ImageResponse imports',
|
|
81
|
-
value: 'next-og-import',
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
title: 'Use viewport export',
|
|
85
|
-
value: 'metadata-to-viewport-export',
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
version: '15.0.0-canary.153',
|
|
91
|
-
codemods: [
|
|
92
|
-
{
|
|
93
|
-
title: 'Migrate `geo` and `ip` properties on `NextRequest`',
|
|
94
|
-
value: 'next-request-geo-ip',
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
version: '15.0.0-canary.171',
|
|
100
|
-
codemods: [
|
|
101
|
-
{
|
|
102
|
-
title: 'Transforms usage of Next.js async Request APIs',
|
|
103
|
-
value: 'next-async-request-api',
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
},
|
|
107
|
-
];
|
|
108
|
-
//# sourceMappingURL=codemods.js.map
|