@next/codemod 15.0.0-rc.0 → 15.0.0

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.
Files changed (33) hide show
  1. package/bin/next-codemod.js +55 -3
  2. package/bin/shared.js +7 -0
  3. package/bin/transform.js +124 -0
  4. package/bin/upgrade.js +473 -0
  5. package/lib/cra-to-next/global-css-transform.js +5 -5
  6. package/lib/cra-to-next/index-to-component.js +5 -5
  7. package/lib/handle-package.js +110 -0
  8. package/lib/install.js +2 -3
  9. package/lib/parser.js +28 -0
  10. package/lib/run-jscodeshift.js +18 -2
  11. package/lib/utils.js +115 -0
  12. package/package.json +15 -7
  13. package/transforms/add-missing-react-import.js +4 -3
  14. package/transforms/app-dir-runtime-config-experimental-edge.js +34 -0
  15. package/transforms/built-in-next-font.js +4 -3
  16. package/transforms/cra-to-next.js +238 -236
  17. package/transforms/lib/async-request-api/index.js +16 -0
  18. package/transforms/lib/async-request-api/next-async-dynamic-api.js +284 -0
  19. package/transforms/lib/async-request-api/next-async-dynamic-prop.js +713 -0
  20. package/transforms/lib/async-request-api/utils.js +473 -0
  21. package/transforms/metadata-to-viewport-export.js +4 -3
  22. package/transforms/name-default-component.js +6 -6
  23. package/transforms/new-link.js +9 -7
  24. package/transforms/next-async-request-api.js +9 -0
  25. package/transforms/next-dynamic-access-named-export.js +66 -0
  26. package/transforms/next-image-experimental.js +12 -15
  27. package/transforms/next-image-to-legacy-image.js +8 -9
  28. package/transforms/next-og-import.js +4 -3
  29. package/transforms/next-request-geo-ip.js +339 -0
  30. package/transforms/url-to-withrouter.js +1 -1
  31. package/transforms/withamp-to-config.js +1 -1
  32. package/bin/cli.js +0 -216
  33. package/lib/uninstall-package.js +0 -32
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ "use strict";
2
3
  /**
3
4
  * Copyright 2015-present, Facebook, Inc.
4
5
  *
@@ -6,7 +7,58 @@
6
7
  * LICENSE file in the root directory of this source tree.
7
8
  *
8
9
  */
9
- // Based on https://github.com/reactjs/react-codemod/blob/dd8671c9a470a2c342b221ec903c574cf31e9f57/bin/react-codemod.js
10
- // next-codemod optional-name-of-transform optional/path/to/src [...options]
11
- require('./cli').run();
10
+ // Based on https://github.com/reactjs/react-codemod/blob/dd8671c9a470a2c342b221ec903c574cf31e9f57/bin/cli.js
11
+ // @next/codemod optional-name-of-transform optional/path/to/src [...options]
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ const commander_1 = require("commander");
14
+ const upgrade_1 = require("./upgrade");
15
+ const transform_1 = require("./transform");
16
+ const shared_1 = require("./shared");
17
+ const packageJson = require('../package.json');
18
+ const program = new commander_1.Command(packageJson.name)
19
+ .description('Codemods for updating Next.js apps.')
20
+ .version(packageJson.version, '-v, --version', 'Output the current version of @next/codemod.')
21
+ .argument('[codemod]', 'Codemod slug to run. See "https://github.com/vercel/next.js/tree/canary/packages/next-codemod".')
22
+ .argument('[source]', 'Path to source files or directory to transform including glob patterns.')
23
+ .usage('[codemod] [source] [options]')
24
+ .helpOption('-h, --help', 'Display this help message.')
25
+ .option('-f, --force', 'Bypass Git safety checks and forcibly run codemods')
26
+ .option('-d, --dry', 'Dry run (no changes are made to files)')
27
+ .option('-p, --print', 'Print transformed files to stdout, useful for development')
28
+ .option('--verbose', 'Show more information about the transform process')
29
+ .option('-j, --jscodeshift', '(Advanced) Pass options directly to jscodeshift')
30
+ .action(transform_1.runTransform)
31
+ .allowUnknownOption()
32
+ // This is needed for options for subcommands to be passed correctly.
33
+ // Because by default the options are not positional, which will pass options
34
+ // to the main command "@next/codemod" even if it was passed after subcommands,
35
+ // e.g. "@next/codemod upgrade --verbose" will be treated as "next-codemod --verbose upgrade"
36
+ // By enabling this, it will respect the position of the options and pass it to subcommands.
37
+ // x-ref: https://github.com/tj/commander.js/pull/1427
38
+ .enablePositionalOptions();
39
+ program
40
+ .command('upgrade')
41
+ .description('Upgrade Next.js apps to desired versions with a single command.')
42
+ .argument('[revision]', 'Specify the target Next.js version using an NPM dist tag (e.g. "latest", "canary", "rc") or an exact version number (e.g. "15.0.0").', packageJson.version.includes('-canary.')
43
+ ? 'canary'
44
+ : packageJson.version.includes('-rc.')
45
+ ? 'rc'
46
+ : 'latest')
47
+ .usage('[revision] [options]')
48
+ .option('--verbose', 'Verbose output', false)
49
+ .action(async (revision, options) => {
50
+ try {
51
+ await (0, upgrade_1.runUpgrade)(revision, options);
52
+ }
53
+ catch (error) {
54
+ if (!options.verbose && error instanceof shared_1.BadInput) {
55
+ console.error(error.message);
56
+ }
57
+ else {
58
+ console.error(error);
59
+ }
60
+ process.exit(1);
61
+ }
62
+ });
63
+ program.parse(process.argv);
12
64
  //# sourceMappingURL=next-codemod.js.map
package/bin/shared.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BadInput = void 0;
4
+ class BadInput extends Error {
5
+ }
6
+ exports.BadInput = BadInput;
7
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1,124 @@
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.transformerDirectory = exports.jscodeshiftExecutable = void 0;
7
+ exports.runTransform = runTransform;
8
+ const execa_1 = __importDefault(require("execa"));
9
+ const globby_1 = __importDefault(require("globby"));
10
+ const prompts_1 = __importDefault(require("prompts"));
11
+ const node_path_1 = require("node:path");
12
+ const handle_package_1 = require("../lib/handle-package");
13
+ const utils_1 = require("../lib/utils");
14
+ function expandFilePathsIfNeeded(filesBeforeExpansion) {
15
+ const shouldExpandFiles = filesBeforeExpansion.some((file) => file.includes('*'));
16
+ return shouldExpandFiles
17
+ ? globby_1.default.sync(filesBeforeExpansion)
18
+ : filesBeforeExpansion;
19
+ }
20
+ exports.jscodeshiftExecutable = require.resolve('.bin/jscodeshift');
21
+ exports.transformerDirectory = (0, node_path_1.join)(__dirname, '../', 'transforms');
22
+ async function runTransform(transform, path, options) {
23
+ let transformer = transform;
24
+ let directory = path;
25
+ if (!options.dry) {
26
+ (0, utils_1.checkGitStatus)(options.force);
27
+ }
28
+ if (transform &&
29
+ !utils_1.TRANSFORMER_INQUIRER_CHOICES.find((x) => x.value === transform)) {
30
+ console.error('Invalid transform choice, pick one of:');
31
+ console.error(utils_1.TRANSFORMER_INQUIRER_CHOICES.map((x) => '- ' + x.value).join('\n'));
32
+ process.exit(1);
33
+ }
34
+ if (!path) {
35
+ const res = await (0, prompts_1.default)({
36
+ type: 'text',
37
+ name: 'path',
38
+ message: 'On which files or directory should the codemods be applied?',
39
+ initial: '.',
40
+ }, { onCancel: utils_1.onCancel });
41
+ directory = res.path;
42
+ }
43
+ if (!transform) {
44
+ const res = await (0, prompts_1.default)({
45
+ type: 'select',
46
+ name: 'transformer',
47
+ message: 'Which transform would you like to apply?',
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 });
56
+ transformer = res.transformer;
57
+ }
58
+ const filesExpanded = expandFilePathsIfNeeded([directory]);
59
+ if (!filesExpanded.length) {
60
+ console.log(`No files found matching "${directory}"`);
61
+ return null;
62
+ }
63
+ const transformerPath = (0, node_path_1.join)(exports.transformerDirectory, `${transformer}.js`);
64
+ if (transformer === 'cra-to-next') {
65
+ // cra-to-next transform doesn't use jscodeshift directly
66
+ return require(transformerPath).default(filesExpanded, options);
67
+ }
68
+ let args = [];
69
+ const { dry, print, runInBand, jscodeshift, verbose } = options;
70
+ if (dry) {
71
+ args.push('--dry');
72
+ }
73
+ if (print) {
74
+ args.push('--print');
75
+ }
76
+ if (runInBand) {
77
+ args.push('--run-in-band');
78
+ }
79
+ if (verbose) {
80
+ args.push('--verbose=2');
81
+ }
82
+ args.push('--no-babel');
83
+ args.push('--ignore-pattern=**/node_modules/**');
84
+ args.push('--ignore-pattern=**/.next/**');
85
+ args.push('--extensions=tsx,ts,jsx,js');
86
+ args = args.concat(['--transform', transformerPath]);
87
+ if (jscodeshift) {
88
+ args = args.concat(jscodeshift);
89
+ }
90
+ args = args.concat(filesExpanded);
91
+ console.log(`Executing command: jscodeshift ${args.join(' ')}`);
92
+ const result = execa_1.default.sync(exports.jscodeshiftExecutable, args, {
93
+ stdio: 'inherit',
94
+ stripFinalNewline: false,
95
+ });
96
+ if (result.failed) {
97
+ throw new Error(`jscodeshift exited with code ${result.exitCode}`);
98
+ }
99
+ if (!dry && transformer === 'built-in-next-font') {
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`');
108
+ (0, handle_package_1.uninstallPackage)('@next/font');
109
+ }
110
+ }
111
+ if (!dry && transformer === 'next-request-geo-ip') {
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
+ }
122
+ }
123
+ }
124
+ //# sourceMappingURL=transform.js.map
package/bin/upgrade.js ADDED
@@ -0,0 +1,473 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.runUpgrade = runUpgrade;
30
+ const os = __importStar(require("os"));
31
+ const prompts_1 = __importDefault(require("prompts"));
32
+ const fs_1 = __importDefault(require("fs"));
33
+ const compare_1 = __importDefault(require("semver/functions/compare"));
34
+ const child_process_1 = require("child_process");
35
+ const path_1 = __importDefault(require("path"));
36
+ const picocolors_1 = __importDefault(require("picocolors"));
37
+ const handle_package_1 = require("../lib/handle-package");
38
+ const transform_1 = require("./transform");
39
+ const utils_1 = require("../lib/utils");
40
+ const shared_1 = require("./shared");
41
+ const optionalNextjsPackages = [
42
+ 'create-next-app',
43
+ 'eslint-config-next',
44
+ '@next/bundle-analyzer',
45
+ '@next/codemod',
46
+ '@next/env',
47
+ '@next/eslint-plugin-next',
48
+ '@next/font',
49
+ '@next/mdx',
50
+ '@next/plugin-storybook',
51
+ '@next/polyfill-module',
52
+ '@next/polyfill-nomodule',
53
+ '@next/swc',
54
+ '@next/react-refresh-utils',
55
+ '@next/third-parties',
56
+ ];
57
+ /**
58
+ * @param query
59
+ * @example loadHighestNPMVersionMatching("react@^18.3.0 || ^19.0.0") === Promise<"19.0.0">
60
+ */
61
+ async function loadHighestNPMVersionMatching(query) {
62
+ const versionsJSON = (0, child_process_1.execSync)(`npm --silent view "${query}" --json --field version`, { encoding: 'utf-8' });
63
+ const versionOrVersions = JSON.parse(versionsJSON);
64
+ if (versionOrVersions.length < 1) {
65
+ throw new Error(`Found no React versions matching "${query}". This is a bug in the upgrade tool.`);
66
+ }
67
+ // npm-view returns an array if there are multiple versions matching the query.
68
+ if (Array.isArray(versionOrVersions)) {
69
+ // The last entry will be the latest version published.
70
+ return versionOrVersions[versionOrVersions.length - 1];
71
+ }
72
+ return versionOrVersions;
73
+ }
74
+ function endMessage() {
75
+ console.log();
76
+ console.log(picocolors_1.default.white(picocolors_1.default.bold(`Please review the local changes and read the Next.js 15 migration guide to complete the migration.`)));
77
+ console.log(picocolors_1.default.underline('https://nextjs.org/docs/canary/app/building-your-application/upgrading/version-15'));
78
+ }
79
+ async function runUpgrade(revision, options) {
80
+ const { verbose } = options;
81
+ const appPackageJsonPath = path_1.default.resolve(process.cwd(), 'package.json');
82
+ let appPackageJson = JSON.parse(fs_1.default.readFileSync(appPackageJsonPath, 'utf8'));
83
+ let targetNextPackageJson;
84
+ const res = await fetch(`https://registry.npmjs.org/next/${revision}`);
85
+ if (res.status === 200) {
86
+ targetNextPackageJson = await res.json();
87
+ }
88
+ const validRevision = targetNextPackageJson !== null &&
89
+ typeof targetNextPackageJson === 'object' &&
90
+ 'version' in targetNextPackageJson &&
91
+ 'peerDependencies' in targetNextPackageJson;
92
+ if (!validRevision) {
93
+ throw new shared_1.BadInput(`Invalid revision provided: "${revision}". Please provide a valid Next.js version or dist-tag (e.g. "latest", "canary", "rc", or "15.0.0").\nCheck available versions at https://www.npmjs.com/package/next?activeTab=versions.`);
94
+ }
95
+ const installedNextVersion = getInstalledNextVersion();
96
+ const targetNextVersion = targetNextPackageJson.version;
97
+ if ((0, compare_1.default)(installedNextVersion, targetNextVersion) === 0) {
98
+ console.log(`${picocolors_1.default.green('✓')} Current Next.js version is already on the target version "v${targetNextVersion}".`);
99
+ endMessage();
100
+ return;
101
+ }
102
+ if ((0, compare_1.default)(installedNextVersion, targetNextVersion) > 0) {
103
+ console.log(`${picocolors_1.default.green('✓')} Current Next.js version is higher than the target version "v${targetNextVersion}".`);
104
+ endMessage();
105
+ return;
106
+ }
107
+ const installedReactVersion = getInstalledReactVersion();
108
+ // Align the prefix spaces
109
+ console.log(` Detected installed versions:`);
110
+ console.log(` - React: v${installedReactVersion}`);
111
+ console.log(` - Next.js: v${installedNextVersion}`);
112
+ let shouldStayOnReact18 = false;
113
+ const usesAppDir = isUsingAppDir(process.cwd());
114
+ const usesPagesDir = isUsingPagesDir(process.cwd());
115
+ const isPureAppRouter = usesAppDir && !usesPagesDir;
116
+ const isMixedApp = usesPagesDir && usesAppDir;
117
+ if (
118
+ // From release v14.3.0-canary.45, Next.js expects the React version to be 19.0.0-beta.0
119
+ // If the user is on a version higher than this but is still on React 18, we ask them
120
+ // if they still want to stay on React 18 after the upgrade.
121
+ // IF THE USER USES APP ROUTER, we expect them to upgrade React to > 19.0.0-beta.0,
122
+ // we should only let the user stay on React 18 if they are using pure Pages Router.
123
+ // x-ref(PR): https://github.com/vercel/next.js/pull/65058
124
+ // x-ref(release): https://github.com/vercel/next.js/releases/tag/v14.3.0-canary.45
125
+ (0, compare_1.default)(targetNextVersion, '14.3.0-canary.45') >= 0 &&
126
+ installedReactVersion.startsWith('18') &&
127
+ // Pure App Router always uses React 19
128
+ // The mixed case is tricky to handle from a types perspective.
129
+ // We'll recommend to upgrade in the prompt but users can decide to try 18.
130
+ !isPureAppRouter) {
131
+ const shouldStayOnReact18Res = await (0, prompts_1.default)({
132
+ type: 'confirm',
133
+ name: 'shouldStayOnReact18',
134
+ message: `Do you prefer to stay on React 18?` +
135
+ (isMixedApp
136
+ ? " Since you're using both pages/ and app/, we recommend upgrading React to use a consistent version throughout your app."
137
+ : ''),
138
+ initial: false,
139
+ active: 'Yes',
140
+ inactive: 'No',
141
+ }, { onCancel: utils_1.onCancel });
142
+ shouldStayOnReact18 = shouldStayOnReact18Res.shouldStayOnReact18;
143
+ }
144
+ // We're resolving a specific version here to avoid including "ugly" version queries
145
+ // in the manifest.
146
+ // E.g. in peerDependencies we could have `^18.2.0 || ^19.0.0 || 20.0.0-canary`
147
+ // If we'd just `npm add` that, the manifest would read the same version query.
148
+ // This is basically a `npm --save-exact react@$versionQuery` that works for every package manager.
149
+ const targetReactVersion = shouldStayOnReact18
150
+ ? '18.3.1'
151
+ : await loadHighestNPMVersionMatching(`react@${targetNextPackageJson.peerDependencies['react']}`);
152
+ if ((0, compare_1.default)(targetNextVersion, '15.0.0-canary') >= 0) {
153
+ await suggestTurbopack(appPackageJson);
154
+ }
155
+ const codemods = await suggestCodemods(installedNextVersion, targetNextVersion);
156
+ const packageManager = (0, handle_package_1.getPkgManager)(process.cwd());
157
+ let shouldRunReactCodemods = false;
158
+ let shouldRunReactTypesCodemods = false;
159
+ let execCommand = 'npx';
160
+ // The following React codemods are for React 19
161
+ if (!shouldStayOnReact18 &&
162
+ (0, compare_1.default)(targetReactVersion, '19.0.0-0') >= 0 &&
163
+ (0, compare_1.default)(installedReactVersion, '19.0.0-0') < 0) {
164
+ shouldRunReactCodemods = await suggestReactCodemods();
165
+ shouldRunReactTypesCodemods = await suggestReactTypesCodemods();
166
+ const execCommandMap = {
167
+ yarn: 'yarn dlx',
168
+ pnpm: 'pnpx',
169
+ bun: 'bunx',
170
+ npm: 'npx',
171
+ };
172
+ execCommand = execCommandMap[packageManager];
173
+ }
174
+ fs_1.default.writeFileSync(appPackageJsonPath, JSON.stringify(appPackageJson, null, 2));
175
+ const dependenciesToInstall = [];
176
+ const devDependenciesToInstall = [];
177
+ const allDependencies = {
178
+ ...appPackageJson.dependencies,
179
+ ...appPackageJson.devDependencies,
180
+ };
181
+ const versionMapping = {
182
+ next: { version: targetNextVersion, required: true },
183
+ react: { version: targetReactVersion, required: true },
184
+ 'react-dom': { version: targetReactVersion, required: true },
185
+ 'react-is': { version: targetReactVersion, required: false },
186
+ };
187
+ for (const optionalNextjsPackage of optionalNextjsPackages) {
188
+ versionMapping[optionalNextjsPackage] = {
189
+ version: targetNextVersion,
190
+ required: false,
191
+ };
192
+ }
193
+ if (targetReactVersion.startsWith('19.0.0-canary') ||
194
+ targetReactVersion.startsWith('19.0.0-beta') ||
195
+ targetReactVersion.startsWith('19.0.0-rc')) {
196
+ const [targetReactTypesVersion, targetReactDOMTypesVersion] = await Promise.all([
197
+ loadHighestNPMVersionMatching(`types-react@rc`),
198
+ loadHighestNPMVersionMatching(`types-react-dom@rc`),
199
+ ]);
200
+ if (allDependencies['@types/react']) {
201
+ versionMapping['@types/react'] = {
202
+ version: `npm:types-react@${targetReactTypesVersion}`,
203
+ required: false,
204
+ };
205
+ }
206
+ if (allDependencies['@types/react-dom']) {
207
+ versionMapping['@types/react-dom'] = {
208
+ version: `npm:types-react-dom@${targetReactDOMTypesVersion}`,
209
+ required: false,
210
+ };
211
+ }
212
+ }
213
+ else {
214
+ const [targetReactTypesVersion, targetReactDOMTypesVersion] = await Promise.all([
215
+ loadHighestNPMVersionMatching(`@types/react@${targetNextPackageJson.peerDependencies['react']}`),
216
+ loadHighestNPMVersionMatching(`@types/react-dom@${targetNextPackageJson.peerDependencies['react']}`),
217
+ ]);
218
+ if (allDependencies['@types/react']) {
219
+ versionMapping['@types/react'] = {
220
+ version: targetReactTypesVersion,
221
+ required: false,
222
+ };
223
+ }
224
+ if (allDependencies['@types/react-dom']) {
225
+ versionMapping['@types/react-dom'] = {
226
+ version: targetReactDOMTypesVersion,
227
+ required: false,
228
+ };
229
+ }
230
+ }
231
+ // Even though we only need those if we alias `@types/react` to types-react,
232
+ // we still do it out of safety due to https://github.com/microsoft/DefinitelyTyped-tools/issues/433.
233
+ const overrides = {};
234
+ if (allDependencies['@types/react']) {
235
+ overrides['@types/react'] = versionMapping['@types/react'].version;
236
+ }
237
+ if (allDependencies['@types/react-dom']) {
238
+ overrides['@types/react-dom'] = versionMapping['@types/react-dom'].version;
239
+ }
240
+ writeOverridesField(appPackageJson, packageManager, overrides);
241
+ for (const [packageName, { version, required }] of Object.entries(versionMapping)) {
242
+ if (appPackageJson.devDependencies?.[packageName]) {
243
+ devDependenciesToInstall.push([packageName, version]);
244
+ }
245
+ else if (required || appPackageJson.dependencies?.[packageName]) {
246
+ dependenciesToInstall.push([packageName, version]);
247
+ }
248
+ }
249
+ console.log(`Upgrading your project to ${picocolors_1.default.blue('Next.js ' + targetNextVersion)}...\n`);
250
+ for (const [dep, version] of dependenciesToInstall) {
251
+ (0, handle_package_1.addPackageDependency)(appPackageJson, dep, version, false);
252
+ }
253
+ for (const [dep, version] of devDependenciesToInstall) {
254
+ (0, handle_package_1.addPackageDependency)(appPackageJson, dep, version, true);
255
+ }
256
+ fs_1.default.writeFileSync(appPackageJsonPath, JSON.stringify(appPackageJson, null, 2) +
257
+ // Common IDE formatters would add a newline as well.
258
+ os.EOL);
259
+ (0, handle_package_1.runInstallation)(packageManager);
260
+ for (const codemod of codemods) {
261
+ await (0, transform_1.runTransform)(codemod, process.cwd(), { force: true, verbose });
262
+ }
263
+ // To reduce user-side burden of selecting which codemods to run as it needs additional
264
+ // understanding of the codemods, we run all of the applicable codemods.
265
+ if (shouldRunReactCodemods) {
266
+ // https://react.dev/blog/2024/04/25/react-19-upgrade-guide#run-all-react-19-codemods
267
+ (0, child_process_1.execSync)(
268
+ // `--no-interactive` skips the interactive prompt that asks for confirmation
269
+ // https://github.com/codemod-com/codemod/blob/c0cf00d13161a0ec0965b6cc6bc5d54076839cc8/apps/cli/src/flags.ts#L160
270
+ `${execCommand} codemod@latest react/19/migration-recipe --no-interactive`, { stdio: 'inherit' });
271
+ }
272
+ if (shouldRunReactTypesCodemods) {
273
+ // https://react.dev/blog/2024/04/25/react-19-upgrade-guide#typescript-changes
274
+ // `--yes` skips prompts and applies all codemods automatically
275
+ // https://github.com/eps1lon/types-react-codemod/blob/8463103233d6b70aad3cd6bee1814001eae51b28/README.md?plain=1#L52
276
+ (0, child_process_1.execSync)(`${execCommand} types-react-codemod@latest --yes preset-19 .`, {
277
+ stdio: 'inherit',
278
+ });
279
+ }
280
+ console.log(); // new line
281
+ if (codemods.length > 0) {
282
+ console.log(`${picocolors_1.default.green('✔')} Codemods have been applied successfully.`);
283
+ }
284
+ endMessage();
285
+ }
286
+ function getInstalledNextVersion() {
287
+ try {
288
+ return require(require.resolve('next/package.json', {
289
+ paths: [process.cwd()],
290
+ })).version;
291
+ }
292
+ catch (error) {
293
+ throw new shared_1.BadInput(`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.`, {
294
+ cause: error,
295
+ });
296
+ }
297
+ }
298
+ function getInstalledReactVersion() {
299
+ try {
300
+ return require(require.resolve('react/package.json', {
301
+ paths: [process.cwd()],
302
+ })).version;
303
+ }
304
+ catch (error) {
305
+ throw new shared_1.BadInput(`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.`, {
306
+ cause: error,
307
+ });
308
+ }
309
+ }
310
+ function isUsingPagesDir(projectPath) {
311
+ return (fs_1.default.existsSync(path_1.default.resolve(projectPath, 'pages')) ||
312
+ fs_1.default.existsSync(path_1.default.resolve(projectPath, 'src/pages')));
313
+ }
314
+ function isUsingAppDir(projectPath) {
315
+ return (fs_1.default.existsSync(path_1.default.resolve(projectPath, 'app')) ||
316
+ fs_1.default.existsSync(path_1.default.resolve(projectPath, 'src/app')));
317
+ }
318
+ /*
319
+ * Heuristics are used to determine whether to Turbopack is enabled or not and
320
+ * to determine how to update the dev script.
321
+ *
322
+ * 1. If the dev script contains `--turbo` option, we assume that Turbopack is
323
+ * already enabled.
324
+ * 2. If the dev script contains the string `next dev`, we replace it to
325
+ * `next dev --turbo`.
326
+ * 3. Otherwise, we ask the user to manually add `--turbo` to their dev command,
327
+ * showing the current dev command as the initial value.
328
+ */
329
+ async function suggestTurbopack(packageJson) {
330
+ const devScript = packageJson.scripts['dev'];
331
+ if (devScript.includes('--turbo'))
332
+ return;
333
+ const responseTurbopack = await (0, prompts_1.default)({
334
+ type: 'confirm',
335
+ name: 'enable',
336
+ message: 'Enable Turbopack for next dev?',
337
+ initial: true,
338
+ }, { onCancel: utils_1.onCancel });
339
+ if (!responseTurbopack.enable) {
340
+ return;
341
+ }
342
+ if (devScript.includes('next dev')) {
343
+ packageJson.scripts['dev'] = devScript.replace('next dev', 'next dev --turbo');
344
+ return;
345
+ }
346
+ console.log(`${picocolors_1.default.yellow('⚠')} Could not find "${picocolors_1.default.bold('next dev')}" in your dev script.`);
347
+ const responseCustomDevScript = await (0, prompts_1.default)({
348
+ type: 'text',
349
+ name: 'customDevScript',
350
+ message: 'Please manually add "--turbo" to your dev command.',
351
+ initial: devScript,
352
+ }, { onCancel: utils_1.onCancel });
353
+ packageJson.scripts['dev'] =
354
+ responseCustomDevScript.customDevScript || devScript;
355
+ }
356
+ async function suggestCodemods(initialNextVersion, targetNextVersion) {
357
+ // example:
358
+ // codemod version: 15.0.0-canary.45
359
+ // 14.3 -> 15.0.0-canary.45: apply
360
+ // 14.3 -> 15.0.0-canary.44: don't apply
361
+ // 15.0.0-canary.44 -> 15.0.0-canary.45: apply
362
+ // 15.0.0-canary.45 -> 15.0.0-canary.46: don't apply
363
+ // 15.0.0-canary.45 -> 15.0.0 : don't apply
364
+ // 15.0.0-canary.44 -> 15.0.0 : apply
365
+ const initialVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.findIndex((codemod) => {
366
+ return (0, compare_1.default)(codemod.version, initialNextVersion) > 0;
367
+ });
368
+ if (initialVersionIndex === -1) {
369
+ return [];
370
+ }
371
+ let targetVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.findIndex((codemod) => (0, compare_1.default)(codemod.version, targetNextVersion) > 0);
372
+ if (targetVersionIndex === -1) {
373
+ targetVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.length;
374
+ }
375
+ const relevantCodemods = utils_1.TRANSFORMER_INQUIRER_CHOICES.slice(initialVersionIndex, targetVersionIndex);
376
+ if (relevantCodemods.length === 0) {
377
+ return [];
378
+ }
379
+ const { codemods } = await (0, prompts_1.default)({
380
+ type: 'multiselect',
381
+ name: 'codemods',
382
+ message: `The following ${picocolors_1.default.blue('codemods')} are recommended for your upgrade. Select the ones to apply.`,
383
+ choices: relevantCodemods.reverse().map(({ title, value, version }) => {
384
+ return {
385
+ title: `(v${version}) ${value}`,
386
+ description: title,
387
+ value,
388
+ selected: true,
389
+ };
390
+ }),
391
+ }, { onCancel: utils_1.onCancel });
392
+ return codemods;
393
+ }
394
+ async function suggestReactCodemods() {
395
+ const { runReactCodemod } = await (0, prompts_1.default)({
396
+ type: 'confirm',
397
+ name: 'runReactCodemod',
398
+ message: 'Would you like to run the React 19 upgrade codemod?',
399
+ initial: true,
400
+ }, { onCancel: utils_1.onCancel });
401
+ return runReactCodemod;
402
+ }
403
+ async function suggestReactTypesCodemods() {
404
+ const { runReactTypesCodemod } = await (0, prompts_1.default)({
405
+ type: 'confirm',
406
+ name: 'runReactTypesCodemod',
407
+ message: 'Would you like to run the React 19 Types upgrade codemod?',
408
+ initial: true,
409
+ }, { onCancel: utils_1.onCancel });
410
+ return runReactTypesCodemod;
411
+ }
412
+ function writeOverridesField(packageJson, packageManager, overrides) {
413
+ const entries = Object.entries(overrides);
414
+ // Avoids writing an empty overrides field into package.json
415
+ // which would be an unnecessary diff.
416
+ if (entries.length === 0) {
417
+ return;
418
+ }
419
+ if (packageManager === 'npm') {
420
+ if (!packageJson.overrides) {
421
+ packageJson.overrides = {};
422
+ }
423
+ for (const [key, value] of entries) {
424
+ packageJson.overrides[key] = value;
425
+ }
426
+ }
427
+ else if (packageManager === 'pnpm') {
428
+ // pnpm supports pnpm.overrides and pnpm.resolutions
429
+ if (packageJson.resolutions) {
430
+ for (const [key, value] of entries) {
431
+ packageJson.resolutions[key] = value;
432
+ }
433
+ }
434
+ else {
435
+ if (!packageJson.pnpm) {
436
+ packageJson.pnpm = {};
437
+ }
438
+ if (!packageJson.pnpm.overrides) {
439
+ packageJson.pnpm.overrides = {};
440
+ }
441
+ for (const [key, value] of entries) {
442
+ packageJson.pnpm.overrides[key] = value;
443
+ }
444
+ }
445
+ }
446
+ else if (packageManager === 'yarn') {
447
+ if (!packageJson.resolutions) {
448
+ packageJson.resolutions = {};
449
+ }
450
+ for (const [key, value] of entries) {
451
+ packageJson.resolutions[key] = value;
452
+ }
453
+ }
454
+ else if (packageManager === 'bun') {
455
+ // bun supports both overrides and resolutions
456
+ // x-ref: https://bun.sh/docs/install/overrides
457
+ if (packageJson.resolutions) {
458
+ for (const [key, value] of entries) {
459
+ packageJson.resolutions[key] = value;
460
+ }
461
+ }
462
+ else {
463
+ // add overrides field if it's missing and add overrides
464
+ if (!packageJson.overrides) {
465
+ packageJson.overrides = {};
466
+ }
467
+ for (const [key, value] of entries) {
468
+ packageJson.overrides[key] = value;
469
+ }
470
+ }
471
+ }
472
+ }
473
+ //# sourceMappingURL=upgrade.js.map