@next/codemod 15.0.0-canary.182 → 15.0.0-canary.184
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/next-codemod.js +10 -2
- package/bin/transform.js +23 -9
- package/bin/upgrade.js +95 -3
- package/lib/cra-to-next/global-css-transform.js +3 -2
- package/lib/cra-to-next/index-to-component.js +3 -2
- package/lib/handle-package.js +6 -1
- package/lib/parser.js +28 -0
- package/lib/run-jscodeshift.js +0 -4
- package/lib/utils.js +5 -5
- package/package.json +1 -1
- package/transforms/add-missing-react-import.js +3 -2
- package/transforms/app-dir-runtime-config-experimental-edge.js +3 -2
- package/transforms/built-in-next-font.js +3 -2
- package/transforms/lib/async-request-api/next-async-dynamic-api.js +3 -2
- package/transforms/lib/async-request-api/next-async-dynamic-prop.js +39 -10
- package/transforms/metadata-to-viewport-export.js +3 -2
- package/transforms/name-default-component.js +3 -2
- package/transforms/new-link.js +3 -2
- package/transforms/next-dynamic-access-named-export.js +3 -2
- package/transforms/next-image-experimental.js +3 -2
- package/transforms/next-image-to-legacy-image.js +3 -2
- package/transforms/next-og-import.js +3 -2
- package/transforms/next-request-geo-ip.js +6 -5
package/bin/next-codemod.js
CHANGED
|
@@ -23,10 +23,18 @@ const program = new commander_1.Command(packageJson.name)
|
|
|
23
23
|
.helpOption('-h, --help', 'Display this help message.')
|
|
24
24
|
.option('-f, --force', 'Bypass Git safety checks and forcibly run codemods')
|
|
25
25
|
.option('-d, --dry', 'Dry run (no changes are made to files)')
|
|
26
|
-
.option('-p, --print', 'Print transformed files to
|
|
26
|
+
.option('-p, --print', 'Print transformed files to stdout, useful for development')
|
|
27
|
+
.option('--verbose', 'Show more information about the transform process')
|
|
27
28
|
.option('-j, --jscodeshift', '(Advanced) Pass options directly to jscodeshift')
|
|
28
29
|
.action(transform_1.runTransform)
|
|
29
|
-
.allowUnknownOption()
|
|
30
|
+
.allowUnknownOption()
|
|
31
|
+
// This is needed for options for subcommands to be passed correctly.
|
|
32
|
+
// Because by default the options are not positional, which will pass options
|
|
33
|
+
// to the main command "@next/codemod" even if it was passed after subcommands,
|
|
34
|
+
// e.g. "@next/codemod upgrade --verbose" will be treated as "next-codemod --verbose upgrade"
|
|
35
|
+
// By enabling this, it will respect the position of the options and pass it to subcommands.
|
|
36
|
+
// x-ref: https://github.com/tj/commander.js/pull/1427
|
|
37
|
+
.enablePositionalOptions();
|
|
30
38
|
program
|
|
31
39
|
.command('upgrade')
|
|
32
40
|
.description('Upgrade Next.js apps to desired versions with a single command.')
|
package/bin/transform.js
CHANGED
|
@@ -66,7 +66,7 @@ async function runTransform(transform, path, options) {
|
|
|
66
66
|
return require(transformerPath).default(filesExpanded, options);
|
|
67
67
|
}
|
|
68
68
|
let args = [];
|
|
69
|
-
const { dry, print, runInBand, jscodeshift } = options;
|
|
69
|
+
const { dry, print, runInBand, jscodeshift, verbose } = options;
|
|
70
70
|
if (dry) {
|
|
71
71
|
args.push('--dry');
|
|
72
72
|
}
|
|
@@ -76,7 +76,10 @@ async function runTransform(transform, path, options) {
|
|
|
76
76
|
if (runInBand) {
|
|
77
77
|
args.push('--run-in-band');
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
if (verbose) {
|
|
80
|
+
args.push('--verbose=2');
|
|
81
|
+
}
|
|
82
|
+
args.push('--no-babel');
|
|
80
83
|
args.push('--ignore-pattern=**/node_modules/**');
|
|
81
84
|
args.push('--ignore-pattern=**/.next/**');
|
|
82
85
|
args.push('--extensions=tsx,ts,jsx,js');
|
|
@@ -94,17 +97,28 @@ async function runTransform(transform, path, options) {
|
|
|
94
97
|
throw new Error(`jscodeshift exited with code ${result.exitCode}`);
|
|
95
98
|
}
|
|
96
99
|
if (!dry && transformer === 'built-in-next-font') {
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
const { uninstallNextFont } = await (0, prompts_1.default)({
|
|
101
|
+
type: 'confirm',
|
|
102
|
+
name: 'uninstallNextFont',
|
|
103
|
+
message: 'Do you want to uninstall `@next/font`?',
|
|
104
|
+
initial: true,
|
|
105
|
+
});
|
|
106
|
+
if (uninstallNextFont) {
|
|
107
|
+
console.log('Uninstalling `@next/font`');
|
|
99
108
|
(0, handle_package_1.uninstallPackage)('@next/font');
|
|
100
109
|
}
|
|
101
|
-
catch {
|
|
102
|
-
console.error("Couldn't uninstall `@next/font`, please uninstall it manually");
|
|
103
|
-
}
|
|
104
110
|
}
|
|
105
111
|
if (!dry && transformer === 'next-request-geo-ip') {
|
|
106
|
-
|
|
107
|
-
|
|
112
|
+
const { installVercelFunctions } = await (0, prompts_1.default)({
|
|
113
|
+
type: 'confirm',
|
|
114
|
+
name: 'installVercelFunctions',
|
|
115
|
+
message: 'Do you want to install `@vercel/functions`?',
|
|
116
|
+
initial: true,
|
|
117
|
+
});
|
|
118
|
+
if (installVercelFunctions) {
|
|
119
|
+
console.log('Installing `@vercel/functions`...');
|
|
120
|
+
(0, handle_package_1.installPackages)(['@vercel/functions']);
|
|
121
|
+
}
|
|
108
122
|
}
|
|
109
123
|
}
|
|
110
124
|
//# sourceMappingURL=transform.js.map
|
package/bin/upgrade.js
CHANGED
|
@@ -53,18 +53,59 @@ async function runUpgrade(revision, options) {
|
|
|
53
53
|
console.log(`${picocolors_1.default.green('✓')} Current Next.js version is already on or higher than the target version "v${targetNextVersion}".`);
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
|
+
const installedReactVersion = getInstalledReactVersion();
|
|
57
|
+
console.log(`Current React version: v${installedReactVersion}`);
|
|
58
|
+
let shouldStayOnReact18 = false;
|
|
59
|
+
if (
|
|
60
|
+
// From release v14.3.0-canary.45, Next.js expects the React version to be 19.0.0-beta.0
|
|
61
|
+
// If the user is on a version higher than this but is still on React 18, we ask them
|
|
62
|
+
// if they still want to stay on React 18 after the upgrade.
|
|
63
|
+
// IF THE USER USES APP ROUTER, we expect them to upgrade React to > 19.0.0-beta.0,
|
|
64
|
+
// we should only let the user stay on React 18 if they are using pure Pages Router.
|
|
65
|
+
// x-ref(PR): https://github.com/vercel/next.js/pull/65058
|
|
66
|
+
// x-ref(release): https://github.com/vercel/next.js/releases/tag/v14.3.0-canary.45
|
|
67
|
+
(0, compare_versions_1.compareVersions)(installedNextVersion, '14.3.0-canary.45') >= 0 &&
|
|
68
|
+
installedReactVersion.startsWith('18')) {
|
|
69
|
+
const shouldStayOnReact18Res = await (0, prompts_1.default)({
|
|
70
|
+
type: 'confirm',
|
|
71
|
+
name: 'shouldStayOnReact18',
|
|
72
|
+
message: `Are you using ${picocolors_1.default.underline('only the Pages Router')} (no App Router) and prefer to stay on React 18?`,
|
|
73
|
+
initial: false,
|
|
74
|
+
active: 'Yes',
|
|
75
|
+
inactive: 'No',
|
|
76
|
+
}, { onCancel: utils_1.onCancel });
|
|
77
|
+
shouldStayOnReact18 = shouldStayOnReact18Res.shouldStayOnReact18;
|
|
78
|
+
}
|
|
56
79
|
// We're resolving a specific version here to avoid including "ugly" version queries
|
|
57
80
|
// in the manifest.
|
|
58
81
|
// E.g. in peerDependencies we could have `^18.2.0 || ^19.0.0 || 20.0.0-canary`
|
|
59
82
|
// If we'd just `npm add` that, the manifest would read the same version query.
|
|
60
83
|
// This is basically a `npm --save-exact react@$versionQuery` that works for every package manager.
|
|
61
|
-
const targetReactVersion =
|
|
84
|
+
const targetReactVersion = shouldStayOnReact18
|
|
85
|
+
? '18.3.1'
|
|
86
|
+
: await loadHighestNPMVersionMatching(`react@${targetNextPackageJson.peerDependencies['react']}`);
|
|
62
87
|
if ((0, compare_versions_1.compareVersions)(targetNextVersion, '15.0.0-canary') >= 0) {
|
|
63
88
|
await suggestTurbopack(appPackageJson);
|
|
64
89
|
}
|
|
65
90
|
const codemods = await suggestCodemods(installedNextVersion, targetNextVersion);
|
|
66
|
-
fs_1.default.writeFileSync(appPackageJsonPath, JSON.stringify(appPackageJson, null, 2));
|
|
67
91
|
const packageManager = (0, handle_package_1.getPkgManager)(process.cwd());
|
|
92
|
+
let shouldRunReactCodemods = false;
|
|
93
|
+
let shouldRunReactTypesCodemods = false;
|
|
94
|
+
let execCommand = 'npx';
|
|
95
|
+
// The following React codemods are for React 19
|
|
96
|
+
if (!shouldStayOnReact18 &&
|
|
97
|
+
(0, compare_versions_1.compareVersions)(targetReactVersion, '19.0.0-beta.0') >= 0) {
|
|
98
|
+
shouldRunReactCodemods = await suggestReactCodemods();
|
|
99
|
+
shouldRunReactTypesCodemods = await suggestReactTypesCodemods();
|
|
100
|
+
const execCommandMap = {
|
|
101
|
+
yarn: 'yarn dlx',
|
|
102
|
+
pnpm: 'pnpx',
|
|
103
|
+
bun: 'bunx',
|
|
104
|
+
npm: 'npx',
|
|
105
|
+
};
|
|
106
|
+
execCommand = execCommandMap[packageManager];
|
|
107
|
+
}
|
|
108
|
+
fs_1.default.writeFileSync(appPackageJsonPath, JSON.stringify(appPackageJson, null, 2));
|
|
68
109
|
const nextDependency = `next@${targetNextVersion}`;
|
|
69
110
|
const reactDependencies = [
|
|
70
111
|
`react@${targetReactVersion}`,
|
|
@@ -90,7 +131,24 @@ async function runUpgrade(revision, options) {
|
|
|
90
131
|
silent: !verbose,
|
|
91
132
|
});
|
|
92
133
|
for (const codemod of codemods) {
|
|
93
|
-
await (0, transform_1.runTransform)(codemod, process.cwd(), { force: true });
|
|
134
|
+
await (0, transform_1.runTransform)(codemod, process.cwd(), { force: true, verbose });
|
|
135
|
+
}
|
|
136
|
+
// To reduce user-side burden of selecting which codemods to run as it needs additional
|
|
137
|
+
// understanding of the codemods, we run all of the applicable codemods.
|
|
138
|
+
if (shouldRunReactCodemods) {
|
|
139
|
+
// https://react.dev/blog/2024/04/25/react-19-upgrade-guide#run-all-react-19-codemods
|
|
140
|
+
(0, child_process_1.execSync)(
|
|
141
|
+
// `--no-interactive` skips the interactive prompt that asks for confirmation
|
|
142
|
+
// https://github.com/codemod-com/codemod/blob/c0cf00d13161a0ec0965b6cc6bc5d54076839cc8/apps/cli/src/flags.ts#L160
|
|
143
|
+
`${execCommand} codemod@latest react/19/migration-recipe --no-interactive`, { stdio: 'inherit' });
|
|
144
|
+
}
|
|
145
|
+
if (shouldRunReactTypesCodemods) {
|
|
146
|
+
// https://react.dev/blog/2024/04/25/react-19-upgrade-guide#typescript-changes
|
|
147
|
+
// `--yes` skips prompts and applies all codemods automatically
|
|
148
|
+
// https://github.com/eps1lon/types-react-codemod/blob/8463103233d6b70aad3cd6bee1814001eae51b28/README.md?plain=1#L52
|
|
149
|
+
(0, child_process_1.execSync)(`${execCommand} types-react-codemod@latest --yes preset-19 .`, {
|
|
150
|
+
stdio: 'inherit',
|
|
151
|
+
});
|
|
94
152
|
}
|
|
95
153
|
console.log(); // new line
|
|
96
154
|
if (codemods.length > 0) {
|
|
@@ -110,6 +168,18 @@ function getInstalledNextVersion() {
|
|
|
110
168
|
});
|
|
111
169
|
}
|
|
112
170
|
}
|
|
171
|
+
function getInstalledReactVersion() {
|
|
172
|
+
try {
|
|
173
|
+
return require(require.resolve('react/package.json', {
|
|
174
|
+
paths: [process.cwd()],
|
|
175
|
+
})).version;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
throw new Error(`Failed to detect the installed React version in "${process.cwd()}".\nIf you're working in a monorepo, please run this command from the Next.js app directory.`, {
|
|
179
|
+
cause: error,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
113
183
|
/*
|
|
114
184
|
* Heuristics are used to determine whether to Turbopack is enabled or not and
|
|
115
185
|
* to determine how to update the dev script.
|
|
@@ -176,4 +246,26 @@ async function suggestCodemods(initialNextVersion, targetNextVersion) {
|
|
|
176
246
|
}, { onCancel: utils_1.onCancel });
|
|
177
247
|
return codemods;
|
|
178
248
|
}
|
|
249
|
+
async function suggestReactCodemods() {
|
|
250
|
+
const { runReactCodemod } = await (0, prompts_1.default)({
|
|
251
|
+
type: 'toggle',
|
|
252
|
+
name: 'runReactCodemod',
|
|
253
|
+
message: 'Would you like to run the React 19 upgrade codemod?',
|
|
254
|
+
initial: true,
|
|
255
|
+
active: 'Yes',
|
|
256
|
+
inactive: 'No',
|
|
257
|
+
}, { onCancel: utils_1.onCancel });
|
|
258
|
+
return runReactCodemod;
|
|
259
|
+
}
|
|
260
|
+
async function suggestReactTypesCodemods() {
|
|
261
|
+
const { runReactTypesCodemod } = await (0, prompts_1.default)({
|
|
262
|
+
type: 'toggle',
|
|
263
|
+
name: 'runReactTypesCodemod',
|
|
264
|
+
message: 'Would you like to run the React 19 Types upgrade codemod?',
|
|
265
|
+
initial: true,
|
|
266
|
+
active: 'Yes',
|
|
267
|
+
inactive: 'No',
|
|
268
|
+
}, { onCancel: utils_1.onCancel });
|
|
269
|
+
return runReactTypesCodemod;
|
|
270
|
+
}
|
|
179
271
|
//# sourceMappingURL=upgrade.js.map
|
|
@@ -6,13 +6,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.globalCssContext = void 0;
|
|
7
7
|
exports.default = transformer;
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const parser_1 = require("../parser");
|
|
9
10
|
exports.globalCssContext = {
|
|
10
11
|
cssImports: new Set(),
|
|
11
12
|
reactSvgImports: new Set(),
|
|
12
13
|
};
|
|
13
14
|
const globalStylesRegex = /(?<!\.module)\.(css|scss|sass)$/i;
|
|
14
|
-
function transformer(file,
|
|
15
|
-
const j =
|
|
15
|
+
function transformer(file, _api, options) {
|
|
16
|
+
const j = (0, parser_1.createParserFromPath)(file.path);
|
|
16
17
|
const root = j(file.source);
|
|
17
18
|
let hasModifications = false;
|
|
18
19
|
root
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.indexContext = void 0;
|
|
4
4
|
exports.default = transformer;
|
|
5
|
+
const parser_1 = require("../parser");
|
|
5
6
|
exports.indexContext = {
|
|
6
7
|
multipleRenderRoots: false,
|
|
7
8
|
nestedRender: false,
|
|
8
9
|
};
|
|
9
|
-
function transformer(file,
|
|
10
|
-
const j =
|
|
10
|
+
function transformer(file, _api, options) {
|
|
11
|
+
const j = (0, parser_1.createParserFromPath)(file.path);
|
|
11
12
|
const root = j(file.source);
|
|
12
13
|
let hasModifications = false;
|
|
13
14
|
let foundReactRender = 0;
|
package/lib/handle-package.js
CHANGED
|
@@ -39,7 +39,12 @@ function uninstallPackage(packageToUninstall, pkgManager) {
|
|
|
39
39
|
if (pkgManager === 'yarn') {
|
|
40
40
|
command = 'remove';
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
try {
|
|
43
|
+
execa_1.default.sync(pkgManager, [command, packageToUninstall], { stdio: 'inherit' });
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw new Error(`Failed to uninstall "${packageToUninstall}". Please uninstall it manually.`, { cause: error });
|
|
47
|
+
}
|
|
43
48
|
}
|
|
44
49
|
function installPackages(packageToInstall, options = {}) {
|
|
45
50
|
const { packageManager = getPkgManager(process.cwd()), silent = false } = options;
|
package/lib/parser.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createParserFromPath = createParserFromPath;
|
|
7
|
+
const jscodeshift_1 = __importDefault(require("jscodeshift"));
|
|
8
|
+
const babylon_1 = __importDefault(require("jscodeshift/parser/babylon"));
|
|
9
|
+
const tsOptions_1 = __importDefault(require("jscodeshift/parser/tsOptions"));
|
|
10
|
+
const dtsOptions = {
|
|
11
|
+
...tsOptions_1.default,
|
|
12
|
+
plugins: [
|
|
13
|
+
...tsOptions_1.default.plugins.filter((plugin) => plugin !== 'typescript'),
|
|
14
|
+
['typescript', { dts: true }],
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
function createParserFromPath(filePath) {
|
|
18
|
+
const isDeclarationFile = /\.d\.(m|c)?ts$/.test(filePath);
|
|
19
|
+
if (isDeclarationFile) {
|
|
20
|
+
return jscodeshift_1.default.withParser((0, babylon_1.default)(dtsOptions));
|
|
21
|
+
}
|
|
22
|
+
// jsx is allowed in .js files, feed them into the tsx parser.
|
|
23
|
+
// tsx parser :.js, .jsx, .tsx
|
|
24
|
+
// ts parser: .ts, .mts, .cts
|
|
25
|
+
const isTsFile = /\.(m|c)?.ts$/.test(filePath);
|
|
26
|
+
return isTsFile ? jscodeshift_1.default.withParser('ts') : jscodeshift_1.default.withParser('tsx');
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=parser.js.map
|
package/lib/run-jscodeshift.js
CHANGED
package/lib/utils.js
CHANGED
|
@@ -76,6 +76,11 @@ exports.TRANSFORMER_INQUIRER_CHOICES = [
|
|
|
76
76
|
value: 'next-image-to-legacy-image',
|
|
77
77
|
version: '13.0',
|
|
78
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',
|
|
83
|
+
},
|
|
79
84
|
{
|
|
80
85
|
title: 'Uninstall `@next/font` and transform imports to `next/font`',
|
|
81
86
|
value: 'built-in-next-font',
|
|
@@ -106,10 +111,5 @@ exports.TRANSFORMER_INQUIRER_CHOICES = [
|
|
|
106
111
|
value: 'next-async-request-api',
|
|
107
112
|
version: '15.0.0-canary.171',
|
|
108
113
|
},
|
|
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,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = transformer;
|
|
4
|
+
const parser_1 = require("../lib/parser");
|
|
4
5
|
function addReactImport(j, root) {
|
|
5
6
|
// We create an import specifier, this is the value of an import, eg:
|
|
6
7
|
// import React from 'react'
|
|
@@ -34,8 +35,8 @@ function addReactImport(j, root) {
|
|
|
34
35
|
node.value.body.unshift(ReactImport);
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
|
-
function transformer(file,
|
|
38
|
-
const j =
|
|
38
|
+
function transformer(file, _api, options) {
|
|
39
|
+
const j = (0, parser_1.createParserFromPath)(file.path);
|
|
39
40
|
const root = j(file.source);
|
|
40
41
|
const hasReactImport = (r) => {
|
|
41
42
|
return (r.find(j.ImportDefaultSpecifier, {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = transformer;
|
|
4
|
-
|
|
4
|
+
const parser_1 = require("../lib/parser");
|
|
5
|
+
function transformer(file, _api) {
|
|
5
6
|
if (process.env.NODE_ENV !== 'test' &&
|
|
6
7
|
!/[/\\]app[/\\].*?(page|layout|route)\.[^/\\]+$/.test(file.path)) {
|
|
7
8
|
return file.source;
|
|
8
9
|
}
|
|
9
|
-
const j =
|
|
10
|
+
const j = (0, parser_1.createParserFromPath)(file.path);
|
|
10
11
|
const root = j(file.source);
|
|
11
12
|
const runtimeExport = root.find(j.ExportNamedDeclaration, {
|
|
12
13
|
declaration: {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = transformer;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const parser_1 = require("../lib/parser");
|
|
5
|
+
function transformer(file, _api, options) {
|
|
6
|
+
const j = (0, parser_1.createParserFromPath)(file.path);
|
|
6
7
|
const root = j(file.source);
|
|
7
8
|
let hasChanges = false;
|
|
8
9
|
// Before: import { ... } from '@next/font'
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.transformDynamicAPI = transformDynamicAPI;
|
|
4
4
|
const utils_1 = require("./utils");
|
|
5
|
+
const parser_1 = require("../../../lib/parser");
|
|
5
6
|
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
7
|
function findDynamicImportsAndComment(root, j) {
|
|
7
8
|
let modified = false;
|
|
@@ -22,9 +23,9 @@ function findDynamicImportsAndComment(root, j) {
|
|
|
22
23
|
});
|
|
23
24
|
return modified;
|
|
24
25
|
}
|
|
25
|
-
function transformDynamicAPI(source,
|
|
26
|
+
function transformDynamicAPI(source, _api, filePath) {
|
|
26
27
|
const isEntryFile = utils_1.NEXTJS_ENTRY_FILES.test(filePath);
|
|
27
|
-
const j =
|
|
28
|
+
const j = (0, parser_1.createParserFromPath)(filePath);
|
|
28
29
|
const root = j(source);
|
|
29
30
|
let modified = false;
|
|
30
31
|
// Check if 'use' from 'react' needs to be imported
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.transformDynamicProps = transformDynamicProps;
|
|
4
4
|
const utils_1 = require("./utils");
|
|
5
|
+
const parser_1 = require("../../../lib/parser");
|
|
5
6
|
const PAGE_PROPS = 'props';
|
|
6
7
|
function findFunctionBody(path) {
|
|
7
8
|
let functionBody = path.node.body;
|
|
@@ -23,6 +24,12 @@ function awaitMemberAccessOfProp(propIdName, path, j) {
|
|
|
23
24
|
// await each member access
|
|
24
25
|
memberAccess.forEach((memberAccessPath) => {
|
|
25
26
|
const member = memberAccessPath.value;
|
|
27
|
+
const memberProperty = member.property;
|
|
28
|
+
const isAccessingMatchedProperty = j.Identifier.check(memberProperty) &&
|
|
29
|
+
utils_1.TARGET_PROP_NAMES.has(memberProperty.name);
|
|
30
|
+
if (!isAccessingMatchedProperty) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
26
33
|
if (isParentPromiseAllCallExpression(memberAccessPath, j)) {
|
|
27
34
|
return;
|
|
28
35
|
}
|
|
@@ -238,14 +245,14 @@ function modifyTypes(paramTypeAnnotation, propsIdentifier, root, j) {
|
|
|
238
245
|
}
|
|
239
246
|
return modified;
|
|
240
247
|
}
|
|
241
|
-
function transformDynamicProps(source,
|
|
248
|
+
function transformDynamicProps(source, _api, filePath) {
|
|
242
249
|
const isEntryFile = utils_1.NEXTJS_ENTRY_FILES.test(filePath);
|
|
243
250
|
if (!isEntryFile) {
|
|
244
251
|
return null;
|
|
245
252
|
}
|
|
246
253
|
let modified = false;
|
|
247
254
|
let modifiedPropArgument = false;
|
|
248
|
-
const j =
|
|
255
|
+
const j = (0, parser_1.createParserFromPath)(filePath);
|
|
249
256
|
const root = j(source);
|
|
250
257
|
// Check if 'use' from 'react' needs to be imported
|
|
251
258
|
let needsReactUseImport = false;
|
|
@@ -494,20 +501,42 @@ function transformDynamicProps(source, api, filePath) {
|
|
|
494
501
|
continue;
|
|
495
502
|
}
|
|
496
503
|
}
|
|
497
|
-
const
|
|
504
|
+
const paramsPropertyName = j.Identifier.check(paramsProperty)
|
|
498
505
|
? paramsProperty.name
|
|
499
506
|
: null;
|
|
500
|
-
const
|
|
507
|
+
const paramPropertyName = paramsPropertyName || matchedPropName;
|
|
501
508
|
// if propName is not used in lower scope, and it stars with unused prefix `_`,
|
|
502
509
|
// also skip the transformation
|
|
503
510
|
const functionBodyPath = path.get('body');
|
|
504
511
|
const hasUsedInBody = j(functionBodyPath)
|
|
505
512
|
.find(j.Identifier, {
|
|
506
|
-
name:
|
|
513
|
+
name: paramPropertyName,
|
|
507
514
|
})
|
|
508
515
|
.size() > 0;
|
|
509
|
-
if (!hasUsedInBody &&
|
|
516
|
+
if (!hasUsedInBody && paramPropertyName.startsWith('_'))
|
|
510
517
|
continue;
|
|
518
|
+
// Search the usage of propName in the function body,
|
|
519
|
+
// if they're all awaited or wrapped with use(), skip the transformation
|
|
520
|
+
const propUsages = j(functionBodyPath).find(j.Identifier, {
|
|
521
|
+
name: paramPropertyName,
|
|
522
|
+
});
|
|
523
|
+
// if there's usage of the propName, then do the check
|
|
524
|
+
if (propUsages.size()) {
|
|
525
|
+
let hasMissingAwaited = false;
|
|
526
|
+
propUsages.forEach((propUsage) => {
|
|
527
|
+
// If the parent is not AwaitExpression, it's not awaited
|
|
528
|
+
const isAwaited = propUsage.parentPath?.value.type === 'AwaitExpression';
|
|
529
|
+
const isAwaitedByUse = isParentUseCallExpression(propUsage, j);
|
|
530
|
+
if (!isAwaited && !isAwaitedByUse) {
|
|
531
|
+
hasMissingAwaited = true;
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
// If all the usages of parm are awaited, skip the transformation
|
|
536
|
+
if (!hasMissingAwaited) {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
511
540
|
modifiedPropertyCount++;
|
|
512
541
|
const propNameIdentifier = j.identifier(matchedPropName);
|
|
513
542
|
const propsIdentifier = j.identifier(propsIdentifierName);
|
|
@@ -516,7 +545,7 @@ function transformDynamicProps(source, api, filePath) {
|
|
|
516
545
|
// e.g.
|
|
517
546
|
// input: Page({ params: { slug } })
|
|
518
547
|
// output: const { slug } = await props.params; rather than const props = await props.params;
|
|
519
|
-
const uid = functionName + ':' +
|
|
548
|
+
const uid = functionName + ':' + paramPropertyName;
|
|
520
549
|
if (paramsProperty?.type === 'ObjectPattern') {
|
|
521
550
|
const objectPattern = paramsProperty;
|
|
522
551
|
const objectPatternProperties = objectPattern.properties;
|
|
@@ -538,7 +567,7 @@ function transformDynamicProps(source, api, filePath) {
|
|
|
538
567
|
if (isAsyncFunc) {
|
|
539
568
|
// If it's async function, add await to the async props.<propName>
|
|
540
569
|
const paramAssignment = j.variableDeclaration('const', [
|
|
541
|
-
j.variableDeclarator(j.identifier(
|
|
570
|
+
j.variableDeclarator(j.identifier(paramPropertyName), j.awaitExpression(accessedPropIdExpr)),
|
|
542
571
|
]);
|
|
543
572
|
if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
|
|
544
573
|
functionBody.unshift(paramAssignment);
|
|
@@ -556,7 +585,7 @@ function transformDynamicProps(source, api, filePath) {
|
|
|
556
585
|
(0, utils_1.turnFunctionReturnTypeToAsync)(node, j);
|
|
557
586
|
// Insert `const <propName> = await props.<propName>;` at the beginning of the function body
|
|
558
587
|
const paramAssignment = j.variableDeclaration('const', [
|
|
559
|
-
j.variableDeclarator(j.identifier(
|
|
588
|
+
j.variableDeclarator(j.identifier(paramPropertyName), j.awaitExpression(accessedPropIdExpr)),
|
|
560
589
|
]);
|
|
561
590
|
if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
|
|
562
591
|
functionBody.unshift(paramAssignment);
|
|
@@ -566,7 +595,7 @@ function transformDynamicProps(source, api, filePath) {
|
|
|
566
595
|
}
|
|
567
596
|
else {
|
|
568
597
|
const paramAssignment = j.variableDeclaration('const', [
|
|
569
|
-
j.variableDeclarator(j.identifier(
|
|
598
|
+
j.variableDeclarator(j.identifier(paramPropertyName), j.callExpression(j.identifier('use'), [accessedPropIdExpr])),
|
|
570
599
|
]);
|
|
571
600
|
if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
|
|
572
601
|
needsReactUseImport = true;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = transformer;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const parser_1 = require("../lib/parser");
|
|
5
|
+
function transformer(file, _api) {
|
|
6
|
+
const j = (0, parser_1.createParserFromPath)(file.path);
|
|
6
7
|
const root = j(file.source);
|
|
7
8
|
// Find the metadata export
|
|
8
9
|
const metadataExport = root.find(j.ExportNamedDeclaration, {
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = transformer;
|
|
4
4
|
const path_1 = require("path");
|
|
5
|
+
const parser_1 = require("../lib/parser");
|
|
5
6
|
const camelCase = (value) => {
|
|
6
7
|
const val = value.replace(/[-_\s.]+(.)?/g, (_match, chr) => chr ? chr.toUpperCase() : '');
|
|
7
8
|
return val.slice(0, 1).toUpperCase() + val.slice(1);
|
|
8
9
|
};
|
|
9
10
|
const isValidIdentifier = (value) => /^[a-zA-ZÀ-ÿ][0-9a-zA-ZÀ-ÿ]+$/.test(value);
|
|
10
|
-
function transformer(file,
|
|
11
|
-
const j =
|
|
11
|
+
function transformer(file, _api, options) {
|
|
12
|
+
const j = (0, parser_1.createParserFromPath)(file.path);
|
|
12
13
|
const root = j(file.source);
|
|
13
14
|
let hasModifications;
|
|
14
15
|
const returnsJSX = (node) => node.type === 'JSXElement' ||
|
package/transforms/new-link.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
// x-ref: https://github.com/facebook/jscodeshift/issues/534
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.default = transformer;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const parser_1 = require("../lib/parser");
|
|
7
|
+
function transformer(file, _api) {
|
|
8
|
+
const j = (0, parser_1.createParserFromPath)(file.path);
|
|
8
9
|
const $j = j(file.source);
|
|
9
10
|
return $j
|
|
10
11
|
.find(j.ImportDeclaration, { source: { value: 'next/link' } })
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = transformer;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const parser_1 = require("../lib/parser");
|
|
5
|
+
function transformer(file, _api) {
|
|
6
|
+
const j = (0, parser_1.createParserFromPath)(file.path);
|
|
6
7
|
const root = j(file.source);
|
|
7
8
|
// Find the import declaration for 'next/dynamic'
|
|
8
9
|
const dynamicImportDeclaration = root.find(j.ImportDeclaration, {
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.default = transformer;
|
|
4
4
|
const path_1 = require("path");
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
|
+
const parser_1 = require("../lib/parser");
|
|
6
7
|
function findAndReplaceProps(j, root, tagName) {
|
|
7
8
|
const layoutToStyle = {
|
|
8
9
|
intrinsic: { maxWidth: '100%', height: 'auto' },
|
|
@@ -188,8 +189,8 @@ function nextConfigTransformer(j, root, appDir) {
|
|
|
188
189
|
});
|
|
189
190
|
return root;
|
|
190
191
|
}
|
|
191
|
-
function transformer(file,
|
|
192
|
-
const j =
|
|
192
|
+
function transformer(file, _api, options) {
|
|
193
|
+
const j = (0, parser_1.createParserFromPath)(file.path);
|
|
193
194
|
const root = j(file.source);
|
|
194
195
|
const parsed = (0, path_1.parse)(file.path || '/');
|
|
195
196
|
const isConfig = parsed.base === 'next.config.js' ||
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = transformer;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const parser_1 = require("../lib/parser");
|
|
5
|
+
function transformer(file, _api, options) {
|
|
6
|
+
const j = (0, parser_1.createParserFromPath)(file.path);
|
|
6
7
|
const root = j(file.source);
|
|
7
8
|
// Before: import Image from "next/image"
|
|
8
9
|
// After: import Image from "next/legacy/image"
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = transformer;
|
|
4
|
+
const parser_1 = require("../lib/parser");
|
|
4
5
|
const importToChange = 'ImageResponse';
|
|
5
|
-
function transformer(file,
|
|
6
|
-
const j =
|
|
6
|
+
function transformer(file, _api) {
|
|
7
|
+
const j = (0, parser_1.createParserFromPath)(file.path);
|
|
7
8
|
// Find import declarations that match the pattern
|
|
8
9
|
file.source = j(file.source)
|
|
9
10
|
.find(j.ImportDeclaration, {
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = default_1;
|
|
4
|
+
const parser_1 = require("../lib/parser");
|
|
4
5
|
const GEO = 'geo';
|
|
5
6
|
const IP = 'ip';
|
|
6
7
|
const GEOLOCATION = 'geolocation';
|
|
7
8
|
const IP_ADDRESS = 'ipAddress';
|
|
8
9
|
const GEO_TYPE = 'Geo';
|
|
9
|
-
function default_1(
|
|
10
|
-
const j =
|
|
11
|
-
const root = j(
|
|
10
|
+
function default_1(file, _api) {
|
|
11
|
+
const j = (0, parser_1.createParserFromPath)(file.path);
|
|
12
|
+
const root = j(file.source);
|
|
12
13
|
if (!root.length) {
|
|
13
|
-
return
|
|
14
|
+
return file.source;
|
|
14
15
|
}
|
|
15
16
|
const nextReqType = root
|
|
16
17
|
.find(j.FunctionDeclaration)
|
|
@@ -59,7 +60,7 @@ function default_1(fileInfo, api) {
|
|
|
59
60
|
needImportGeoType ||
|
|
60
61
|
hasChangedIpType;
|
|
61
62
|
if (!needChanges) {
|
|
62
|
-
return
|
|
63
|
+
return file.source;
|
|
63
64
|
}
|
|
64
65
|
// Even if there was a change above, if there's an existing import,
|
|
65
66
|
// we don't need to import them again.
|