@next/codemod 15.0.0-canary.183 → 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 +119 -8
- package/lib/cra-to-next/global-css-transform.js +3 -2
- package/lib/cra-to-next/index-to-component.js +3 -2
- package/lib/parser.js +28 -0
- package/lib/run-jscodeshift.js +0 -4
- package/lib/utils.js +16 -16
- package/package.json +5 -4
- 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 +6 -5
- package/transforms/lib/async-request-api/next-async-dynamic-prop.js +7 -6
- package/transforms/lib/async-request-api/utils.js +37 -7
- 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/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,22 +50,67 @@ 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}".`);
|
|
54
55
|
return;
|
|
55
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}".`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const installedReactVersion = getInstalledReactVersion();
|
|
62
|
+
console.log(`Current React version: v${installedReactVersion}`);
|
|
63
|
+
let shouldStayOnReact18 = false;
|
|
64
|
+
if (
|
|
65
|
+
// From release v14.3.0-canary.45, Next.js expects the React version to be 19.0.0-beta.0
|
|
66
|
+
// If the user is on a version higher than this but is still on React 18, we ask them
|
|
67
|
+
// if they still want to stay on React 18 after the upgrade.
|
|
68
|
+
// IF THE USER USES APP ROUTER, we expect them to upgrade React to > 19.0.0-beta.0,
|
|
69
|
+
// we should only let the user stay on React 18 if they are using pure Pages Router.
|
|
70
|
+
// x-ref(PR): https://github.com/vercel/next.js/pull/65058
|
|
71
|
+
// x-ref(release): https://github.com/vercel/next.js/releases/tag/v14.3.0-canary.45
|
|
72
|
+
(0, compare_1.default)(installedNextVersion, '14.3.0-canary.45') >= 0 &&
|
|
73
|
+
installedReactVersion.startsWith('18')) {
|
|
74
|
+
const shouldStayOnReact18Res = await (0, prompts_1.default)({
|
|
75
|
+
type: 'confirm',
|
|
76
|
+
name: 'shouldStayOnReact18',
|
|
77
|
+
message: `Are you using ${picocolors_1.default.underline('only the Pages Router')} (no App Router) and prefer to stay on React 18?`,
|
|
78
|
+
initial: false,
|
|
79
|
+
active: 'Yes',
|
|
80
|
+
inactive: 'No',
|
|
81
|
+
}, { onCancel: utils_1.onCancel });
|
|
82
|
+
shouldStayOnReact18 = shouldStayOnReact18Res.shouldStayOnReact18;
|
|
83
|
+
}
|
|
56
84
|
// We're resolving a specific version here to avoid including "ugly" version queries
|
|
57
85
|
// in the manifest.
|
|
58
86
|
// E.g. in peerDependencies we could have `^18.2.0 || ^19.0.0 || 20.0.0-canary`
|
|
59
87
|
// If we'd just `npm add` that, the manifest would read the same version query.
|
|
60
88
|
// This is basically a `npm --save-exact react@$versionQuery` that works for every package manager.
|
|
61
|
-
const targetReactVersion =
|
|
62
|
-
|
|
89
|
+
const targetReactVersion = shouldStayOnReact18
|
|
90
|
+
? '18.3.1'
|
|
91
|
+
: await loadHighestNPMVersionMatching(`react@${targetNextPackageJson.peerDependencies['react']}`);
|
|
92
|
+
if ((0, compare_1.default)(targetNextVersion, '15.0.0-canary') >= 0) {
|
|
63
93
|
await suggestTurbopack(appPackageJson);
|
|
64
94
|
}
|
|
65
95
|
const codemods = await suggestCodemods(installedNextVersion, targetNextVersion);
|
|
66
|
-
fs_1.default.writeFileSync(appPackageJsonPath, JSON.stringify(appPackageJson, null, 2));
|
|
67
96
|
const packageManager = (0, handle_package_1.getPkgManager)(process.cwd());
|
|
97
|
+
let shouldRunReactCodemods = false;
|
|
98
|
+
let shouldRunReactTypesCodemods = false;
|
|
99
|
+
let execCommand = 'npx';
|
|
100
|
+
// The following React codemods are for React 19
|
|
101
|
+
if (!shouldStayOnReact18 &&
|
|
102
|
+
(0, compare_1.default)(targetReactVersion, '19.0.0-beta.0') >= 0) {
|
|
103
|
+
shouldRunReactCodemods = await suggestReactCodemods();
|
|
104
|
+
shouldRunReactTypesCodemods = await suggestReactTypesCodemods();
|
|
105
|
+
const execCommandMap = {
|
|
106
|
+
yarn: 'yarn dlx',
|
|
107
|
+
pnpm: 'pnpx',
|
|
108
|
+
bun: 'bunx',
|
|
109
|
+
npm: 'npx',
|
|
110
|
+
};
|
|
111
|
+
execCommand = execCommandMap[packageManager];
|
|
112
|
+
}
|
|
113
|
+
fs_1.default.writeFileSync(appPackageJsonPath, JSON.stringify(appPackageJson, null, 2));
|
|
68
114
|
const nextDependency = `next@${targetNextVersion}`;
|
|
69
115
|
const reactDependencies = [
|
|
70
116
|
`react@${targetReactVersion}`,
|
|
@@ -92,6 +138,23 @@ async function runUpgrade(revision, options) {
|
|
|
92
138
|
for (const codemod of codemods) {
|
|
93
139
|
await (0, transform_1.runTransform)(codemod, process.cwd(), { force: true, verbose });
|
|
94
140
|
}
|
|
141
|
+
// To reduce user-side burden of selecting which codemods to run as it needs additional
|
|
142
|
+
// understanding of the codemods, we run all of the applicable codemods.
|
|
143
|
+
if (shouldRunReactCodemods) {
|
|
144
|
+
// https://react.dev/blog/2024/04/25/react-19-upgrade-guide#run-all-react-19-codemods
|
|
145
|
+
(0, child_process_1.execSync)(
|
|
146
|
+
// `--no-interactive` skips the interactive prompt that asks for confirmation
|
|
147
|
+
// https://github.com/codemod-com/codemod/blob/c0cf00d13161a0ec0965b6cc6bc5d54076839cc8/apps/cli/src/flags.ts#L160
|
|
148
|
+
`${execCommand} codemod@latest react/19/migration-recipe --no-interactive`, { stdio: 'inherit' });
|
|
149
|
+
}
|
|
150
|
+
if (shouldRunReactTypesCodemods) {
|
|
151
|
+
// https://react.dev/blog/2024/04/25/react-19-upgrade-guide#typescript-changes
|
|
152
|
+
// `--yes` skips prompts and applies all codemods automatically
|
|
153
|
+
// https://github.com/eps1lon/types-react-codemod/blob/8463103233d6b70aad3cd6bee1814001eae51b28/README.md?plain=1#L52
|
|
154
|
+
(0, child_process_1.execSync)(`${execCommand} types-react-codemod@latest --yes preset-19 .`, {
|
|
155
|
+
stdio: 'inherit',
|
|
156
|
+
});
|
|
157
|
+
}
|
|
95
158
|
console.log(); // new line
|
|
96
159
|
if (codemods.length > 0) {
|
|
97
160
|
console.log(`${picocolors_1.default.green('✔')} Codemods have been applied successfully.`);
|
|
@@ -110,6 +173,18 @@ function getInstalledNextVersion() {
|
|
|
110
173
|
});
|
|
111
174
|
}
|
|
112
175
|
}
|
|
176
|
+
function getInstalledReactVersion() {
|
|
177
|
+
try {
|
|
178
|
+
return require(require.resolve('react/package.json', {
|
|
179
|
+
paths: [process.cwd()],
|
|
180
|
+
})).version;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
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.`, {
|
|
184
|
+
cause: error,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
113
188
|
/*
|
|
114
189
|
* Heuristics are used to determine whether to Turbopack is enabled or not and
|
|
115
190
|
* to determine how to update the dev script.
|
|
@@ -149,11 +224,25 @@ async function suggestTurbopack(packageJson) {
|
|
|
149
224
|
responseCustomDevScript.customDevScript || devScript;
|
|
150
225
|
}
|
|
151
226
|
async function suggestCodemods(initialNextVersion, targetNextVersion) {
|
|
152
|
-
|
|
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
|
+
});
|
|
153
242
|
if (initialVersionIndex === -1) {
|
|
154
243
|
return [];
|
|
155
244
|
}
|
|
156
|
-
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);
|
|
157
246
|
if (targetVersionIndex === -1) {
|
|
158
247
|
targetVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.length;
|
|
159
248
|
}
|
|
@@ -176,4 +265,26 @@ async function suggestCodemods(initialNextVersion, targetNextVersion) {
|
|
|
176
265
|
}, { onCancel: utils_1.onCancel });
|
|
177
266
|
return codemods;
|
|
178
267
|
}
|
|
268
|
+
async function suggestReactCodemods() {
|
|
269
|
+
const { runReactCodemod } = await (0, prompts_1.default)({
|
|
270
|
+
type: 'toggle',
|
|
271
|
+
name: 'runReactCodemod',
|
|
272
|
+
message: 'Would you like to run the React 19 upgrade codemod?',
|
|
273
|
+
initial: true,
|
|
274
|
+
active: 'Yes',
|
|
275
|
+
inactive: 'No',
|
|
276
|
+
}, { onCancel: utils_1.onCancel });
|
|
277
|
+
return runReactCodemod;
|
|
278
|
+
}
|
|
279
|
+
async function suggestReactTypesCodemods() {
|
|
280
|
+
const { runReactTypesCodemod } = await (0, prompts_1.default)({
|
|
281
|
+
type: 'toggle',
|
|
282
|
+
name: 'runReactTypesCodemod',
|
|
283
|
+
message: 'Would you like to run the React 19 Types upgrade codemod?',
|
|
284
|
+
initial: true,
|
|
285
|
+
active: 'Yes',
|
|
286
|
+
inactive: 'No',
|
|
287
|
+
}, { onCancel: utils_1.onCancel });
|
|
288
|
+
return runReactTypesCodemod;
|
|
289
|
+
}
|
|
179
290
|
//# 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/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
|
@@ -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
|
}
|
|
@@ -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,7 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.transformDynamicAPI = transformDynamicAPI;
|
|
4
4
|
const utils_1 = require("./utils");
|
|
5
|
-
const
|
|
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
7
|
function findDynamicImportsAndComment(root, j) {
|
|
7
8
|
let modified = false;
|
|
8
9
|
// find all the dynamic imports of `next/headers`,
|
|
@@ -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
|
|
@@ -135,12 +136,12 @@ function transformDynamicAPI(source, api, filePath) {
|
|
|
135
136
|
needsReactUseImport = true;
|
|
136
137
|
}
|
|
137
138
|
else {
|
|
138
|
-
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 `);
|
|
139
140
|
modified ||= casted;
|
|
140
141
|
}
|
|
141
142
|
}
|
|
142
143
|
else {
|
|
143
|
-
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 `);
|
|
144
145
|
modified ||= casted;
|
|
145
146
|
}
|
|
146
147
|
}
|
|
@@ -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;
|
|
@@ -141,7 +142,7 @@ function commentOnMatchedReExports(root, j) {
|
|
|
141
142
|
specifier.exported.name === 'default')) {
|
|
142
143
|
if (j.Literal.check(path.value.source)) {
|
|
143
144
|
const localName = specifier.local.name;
|
|
144
|
-
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\``);
|
|
145
146
|
modified ||= commentInserted;
|
|
146
147
|
}
|
|
147
148
|
else if (path.value.source === null) {
|
|
@@ -154,7 +155,7 @@ function commentOnMatchedReExports(root, j) {
|
|
|
154
155
|
return importPath.value.specifiers.some((importSpecifier) => importSpecifier.local.name === localName);
|
|
155
156
|
});
|
|
156
157
|
if (importDeclaration.size() > 0) {
|
|
157
|
-
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\``);
|
|
158
159
|
modified ||= commentInserted;
|
|
159
160
|
}
|
|
160
161
|
}
|
|
@@ -244,14 +245,14 @@ function modifyTypes(paramTypeAnnotation, propsIdentifier, root, j) {
|
|
|
244
245
|
}
|
|
245
246
|
return modified;
|
|
246
247
|
}
|
|
247
|
-
function transformDynamicProps(source,
|
|
248
|
+
function transformDynamicProps(source, _api, filePath) {
|
|
248
249
|
const isEntryFile = utils_1.NEXTJS_ENTRY_FILES.test(filePath);
|
|
249
250
|
if (!isEntryFile) {
|
|
250
251
|
return null;
|
|
251
252
|
}
|
|
252
253
|
let modified = false;
|
|
253
254
|
let modifiedPropArgument = false;
|
|
254
|
-
const j =
|
|
255
|
+
const j = (0, parser_1.createParserFromPath)(filePath);
|
|
255
256
|
const root = j(source);
|
|
256
257
|
// Check if 'use' from 'react' needs to be imported
|
|
257
258
|
let needsReactUseImport = false;
|
|
@@ -354,7 +355,7 @@ function transformDynamicProps(source, api, filePath) {
|
|
|
354
355
|
// find the argument `currentParam`
|
|
355
356
|
const args = callExpression.value.arguments;
|
|
356
357
|
const propPassedAsArg = args.find((arg) => j.Identifier.check(arg) && arg.name === argName);
|
|
357
|
-
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. `;
|
|
358
359
|
const inserted = (0, utils_1.insertCommentOnce)(propPassedAsArg, j, comment);
|
|
359
360
|
modified ||= inserted;
|
|
360
361
|
});
|
|
@@ -697,7 +698,7 @@ function commentSpreadProps(path, propsIdentifierName, j) {
|
|
|
697
698
|
const objSpreadProperties = functionBodyCollection.find(j.SpreadElement, {
|
|
698
699
|
argument: { name: propsIdentifierName },
|
|
699
700
|
});
|
|
700
|
-
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. `;
|
|
701
702
|
// Add comment before it
|
|
702
703
|
jsxSpreadProperties.forEach((spread) => {
|
|
703
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;
|
|
@@ -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.
|