@next/codemod 15.0.0-canary.19 → 15.0.0-canary.190

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 (32) hide show
  1. package/bin/next-codemod.js +41 -3
  2. package/bin/transform.js +124 -0
  3. package/bin/upgrade.js +290 -0
  4. package/lib/cra-to-next/global-css-transform.js +5 -5
  5. package/lib/cra-to-next/index-to-component.js +5 -5
  6. package/lib/handle-package.js +63 -0
  7. package/lib/install.js +2 -3
  8. package/lib/parser.js +28 -0
  9. package/lib/run-jscodeshift.js +18 -2
  10. package/lib/utils.js +115 -0
  11. package/package.json +12 -6
  12. package/transforms/add-missing-react-import.js +4 -3
  13. package/transforms/app-dir-runtime-config-experimental-edge.js +34 -0
  14. package/transforms/built-in-next-font.js +4 -3
  15. package/transforms/cra-to-next.js +238 -236
  16. package/transforms/lib/async-request-api/index.js +16 -0
  17. package/transforms/lib/async-request-api/next-async-dynamic-api.js +274 -0
  18. package/transforms/lib/async-request-api/next-async-dynamic-prop.js +715 -0
  19. package/transforms/lib/async-request-api/utils.js +374 -0
  20. package/transforms/metadata-to-viewport-export.js +4 -3
  21. package/transforms/name-default-component.js +6 -6
  22. package/transforms/new-link.js +9 -7
  23. package/transforms/next-async-request-api.js +9 -0
  24. package/transforms/next-dynamic-access-named-export.js +66 -0
  25. package/transforms/next-image-experimental.js +12 -15
  26. package/transforms/next-image-to-legacy-image.js +8 -9
  27. package/transforms/next-og-import.js +4 -3
  28. package/transforms/next-request-geo-ip.js +339 -0
  29. package/transforms/url-to-withrouter.js +1 -1
  30. package/transforms/withamp-to-config.js +1 -1
  31. package/bin/cli.js +0 -216
  32. 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,44 @@
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 packageJson = require('../package.json');
17
+ const program = new commander_1.Command(packageJson.name)
18
+ .description('Codemods for updating Next.js apps.')
19
+ .version(packageJson.version, '-v, --version', 'Output the current version of @next/codemod.')
20
+ .argument('[codemod]', 'Codemod slug to run. See "https://github.com/vercel/next.js/tree/canary/packages/next-codemod".')
21
+ .argument('[source]', 'Path to source files or directory to transform including glob patterns.')
22
+ .usage('[codemod] [source] [options]')
23
+ .helpOption('-h, --help', 'Display this help message.')
24
+ .option('-f, --force', 'Bypass Git safety checks and forcibly run codemods')
25
+ .option('-d, --dry', 'Dry run (no changes are made to files)')
26
+ .option('-p, --print', 'Print transformed files to stdout, useful for development')
27
+ .option('--verbose', 'Show more information about the transform process')
28
+ .option('-j, --jscodeshift', '(Advanced) Pass options directly to jscodeshift')
29
+ .action(transform_1.runTransform)
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();
38
+ program
39
+ .command('upgrade')
40
+ .description('Upgrade Next.js apps to desired versions with a single command.')
41
+ .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.')
42
+ ? 'canary'
43
+ : packageJson.version.includes('-rc.')
44
+ ? 'rc'
45
+ : 'latest')
46
+ .usage('[revision] [options]')
47
+ .option('--verbose', 'Verbose output', false)
48
+ .action(upgrade_1.runUpgrade);
49
+ program.parse(process.argv);
12
50
  //# sourceMappingURL=next-codemod.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,290 @@
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.runUpgrade = runUpgrade;
7
+ const prompts_1 = __importDefault(require("prompts"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const semver_1 = __importDefault(require("semver"));
10
+ const compare_1 = __importDefault(require("semver/functions/compare"));
11
+ const child_process_1 = require("child_process");
12
+ const path_1 = __importDefault(require("path"));
13
+ const picocolors_1 = __importDefault(require("picocolors"));
14
+ const handle_package_1 = require("../lib/handle-package");
15
+ const transform_1 = require("./transform");
16
+ const utils_1 = require("../lib/utils");
17
+ /**
18
+ * @param query
19
+ * @example loadHighestNPMVersionMatching("react@^18.3.0 || ^19.0.0") === Promise<"19.0.0">
20
+ */
21
+ async function loadHighestNPMVersionMatching(query) {
22
+ const versionsJSON = (0, child_process_1.execSync)(`npm --silent view "${query}" --json --field version`, { encoding: 'utf-8' });
23
+ const versionOrVersions = JSON.parse(versionsJSON);
24
+ if (versionOrVersions.length < 1) {
25
+ throw new Error(`Found no React versions matching "${query}". This is a bug in the upgrade tool.`);
26
+ }
27
+ // npm-view returns an array if there are multiple versions matching the query.
28
+ if (Array.isArray(versionOrVersions)) {
29
+ // The last entry will be the latest version published.
30
+ return versionOrVersions[versionOrVersions.length - 1];
31
+ }
32
+ return versionOrVersions;
33
+ }
34
+ async function runUpgrade(revision, options) {
35
+ const { verbose } = options;
36
+ const appPackageJsonPath = path_1.default.resolve(process.cwd(), 'package.json');
37
+ let appPackageJson = JSON.parse(fs_1.default.readFileSync(appPackageJsonPath, 'utf8'));
38
+ let targetNextPackageJson;
39
+ const res = await fetch(`https://registry.npmjs.org/next/${revision}`);
40
+ if (res.status === 200) {
41
+ targetNextPackageJson = await res.json();
42
+ }
43
+ const validRevision = targetNextPackageJson !== null &&
44
+ typeof targetNextPackageJson === 'object' &&
45
+ 'version' in targetNextPackageJson &&
46
+ 'peerDependencies' in targetNextPackageJson;
47
+ if (!validRevision) {
48
+ throw new Error(`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.`);
49
+ }
50
+ const installedNextVersion = getInstalledNextVersion();
51
+ console.log(`Current Next.js version: v${installedNextVersion}`);
52
+ const targetNextVersion = targetNextPackageJson.version;
53
+ if ((0, compare_1.default)(installedNextVersion, targetNextVersion) === 0) {
54
+ console.log(`${picocolors_1.default.green('✓')} Current Next.js version is already on the target version "v${targetNextVersion}".`);
55
+ return;
56
+ }
57
+ if ((0, compare_1.default)(installedNextVersion, targetNextVersion) > 0) {
58
+ console.log(`${picocolors_1.default.green('✓')} Current Next.js version is higher than the target version "v${targetNextVersion}".`);
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
+ }
84
+ // We're resolving a specific version here to avoid including "ugly" version queries
85
+ // in the manifest.
86
+ // E.g. in peerDependencies we could have `^18.2.0 || ^19.0.0 || 20.0.0-canary`
87
+ // If we'd just `npm add` that, the manifest would read the same version query.
88
+ // This is basically a `npm --save-exact react@$versionQuery` that works for every package manager.
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) {
93
+ await suggestTurbopack(appPackageJson);
94
+ }
95
+ const codemods = await suggestCodemods(installedNextVersion, targetNextVersion);
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));
114
+ const nextDependency = `next@${targetNextVersion}`;
115
+ const reactDependencies = [
116
+ `react@${targetReactVersion}`,
117
+ `react-dom@${targetReactVersion}`,
118
+ ];
119
+ if (targetReactVersion.startsWith('19.0.0-canary') ||
120
+ targetReactVersion.startsWith('19.0.0-beta') ||
121
+ targetReactVersion.startsWith('19.0.0-rc')) {
122
+ reactDependencies.push(`@types/react@npm:types-react@rc`);
123
+ reactDependencies.push(`@types/react-dom@npm:types-react-dom@rc`);
124
+ }
125
+ else {
126
+ const [targetReactTypesVersion, targetReactDOMTypesVersion] = await Promise.all([
127
+ loadHighestNPMVersionMatching(`@types/react@${targetNextPackageJson.peerDependencies['react']}`),
128
+ loadHighestNPMVersionMatching(`@types/react-dom@${targetNextPackageJson.peerDependencies['react']}`),
129
+ ]);
130
+ reactDependencies.push(`@types/react@${targetReactTypesVersion}`);
131
+ reactDependencies.push(`@types/react-dom@${targetReactDOMTypesVersion}`);
132
+ }
133
+ console.log(`Upgrading your project to ${picocolors_1.default.blue('Next.js ' + targetNextVersion)}...\n`);
134
+ (0, handle_package_1.installPackages)([nextDependency, ...reactDependencies], {
135
+ packageManager,
136
+ silent: !verbose,
137
+ });
138
+ for (const codemod of codemods) {
139
+ await (0, transform_1.runTransform)(codemod, process.cwd(), { force: true, verbose });
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
+ }
158
+ console.log(); // new line
159
+ if (codemods.length > 0) {
160
+ console.log(`${picocolors_1.default.green('✔')} Codemods have been applied successfully.`);
161
+ }
162
+ console.log(`Please review the local changes and read the Next.js 15 migration guide to complete the migration. https://nextjs.org/docs/canary/app/building-your-application/upgrading/version-15`);
163
+ }
164
+ function getInstalledNextVersion() {
165
+ try {
166
+ return require(require.resolve('next/package.json', {
167
+ paths: [process.cwd()],
168
+ })).version;
169
+ }
170
+ catch (error) {
171
+ throw new Error(`Failed to get the installed Next.js version at "${process.cwd()}".\nIf you're using a monorepo, please run this command from the Next.js app directory.`, {
172
+ cause: error,
173
+ });
174
+ }
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
+ }
188
+ /*
189
+ * Heuristics are used to determine whether to Turbopack is enabled or not and
190
+ * to determine how to update the dev script.
191
+ *
192
+ * 1. If the dev script contains `--turbo` option, we assume that Turbopack is
193
+ * already enabled.
194
+ * 2. If the dev script contains the string `next dev`, we replace it to
195
+ * `next dev --turbo`.
196
+ * 3. Otherwise, we ask the user to manually add `--turbo` to their dev command,
197
+ * showing the current dev command as the initial value.
198
+ */
199
+ async function suggestTurbopack(packageJson) {
200
+ const devScript = packageJson.scripts['dev'];
201
+ if (devScript.includes('--turbo'))
202
+ return;
203
+ const responseTurbopack = await (0, prompts_1.default)({
204
+ type: 'confirm',
205
+ name: 'enable',
206
+ message: 'Enable Turbopack for next dev?',
207
+ initial: true,
208
+ }, { onCancel: utils_1.onCancel });
209
+ if (!responseTurbopack.enable) {
210
+ return;
211
+ }
212
+ if (devScript.includes('next dev')) {
213
+ packageJson.scripts['dev'] = devScript.replace('next dev', 'next dev --turbo');
214
+ return;
215
+ }
216
+ console.log(`${picocolors_1.default.yellow('⚠')} Could not find "${picocolors_1.default.bold('next dev')}" in your dev script.`);
217
+ const responseCustomDevScript = await (0, prompts_1.default)({
218
+ type: 'text',
219
+ name: 'customDevScript',
220
+ message: 'Please manually add "--turbo" to your dev command.',
221
+ initial: devScript,
222
+ }, { onCancel: utils_1.onCancel });
223
+ packageJson.scripts['dev'] =
224
+ responseCustomDevScript.customDevScript || devScript;
225
+ }
226
+ async function suggestCodemods(initialNextVersion, targetNextVersion) {
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
+ });
242
+ if (initialVersionIndex === -1) {
243
+ return [];
244
+ }
245
+ let targetVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.findIndex((versionCodemods) => (0, compare_1.default)(versionCodemods.version, targetNextVersion) > 0);
246
+ if (targetVersionIndex === -1) {
247
+ targetVersionIndex = utils_1.TRANSFORMER_INQUIRER_CHOICES.length;
248
+ }
249
+ const relevantCodemods = utils_1.TRANSFORMER_INQUIRER_CHOICES.slice(initialVersionIndex, targetVersionIndex);
250
+ if (relevantCodemods.length === 0) {
251
+ return [];
252
+ }
253
+ const { codemods } = await (0, prompts_1.default)({
254
+ type: 'multiselect',
255
+ name: 'codemods',
256
+ message: `The following ${picocolors_1.default.blue('codemods')} are recommended for your upgrade. Select the ones to apply.`,
257
+ choices: relevantCodemods.reverse().map(({ title, value, version }) => {
258
+ return {
259
+ title: `(v${version}) ${value}`,
260
+ description: title,
261
+ value,
262
+ selected: true,
263
+ };
264
+ }),
265
+ }, { onCancel: utils_1.onCancel });
266
+ return codemods;
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
+ }
290
+ //# sourceMappingURL=upgrade.js.map
@@ -4,14 +4,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.globalCssContext = void 0;
7
+ exports.default = transformer;
7
8
  const path_1 = __importDefault(require("path"));
9
+ const parser_1 = require("../parser");
8
10
  exports.globalCssContext = {
9
11
  cssImports: new Set(),
10
12
  reactSvgImports: new Set(),
11
13
  };
12
14
  const globalStylesRegex = /(?<!\.module)\.(css|scss|sass)$/i;
13
- function transformer(file, api, options) {
14
- const j = api.jscodeshift.withParser('tsx');
15
+ function transformer(file, _api, options) {
16
+ const j = (0, parser_1.createParserFromPath)(file.path);
15
17
  const root = j(file.source);
16
18
  let hasModifications = false;
17
19
  root
@@ -37,8 +39,7 @@ function transformer(file, api, options) {
37
39
  }
38
40
  else if (value.endsWith('.svg')) {
39
41
  const isComponentImport = path.node.specifiers.some((specifier) => {
40
- var _a;
41
- return ((_a = specifier.imported) === null || _a === void 0 ? void 0 : _a.name) === 'ReactComponent';
42
+ return specifier.imported?.name === 'ReactComponent';
42
43
  });
43
44
  if (isComponentImport) {
44
45
  exports.globalCssContext.reactSvgImports.add(file.path);
@@ -52,5 +53,4 @@ function transformer(file, api, options) {
52
53
  ? root.toSource(options)
53
54
  : null;
54
55
  }
55
- exports.default = transformer;
56
56
  //# sourceMappingURL=global-css-transform.js.map
@@ -1,12 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.indexContext = void 0;
4
+ exports.default = transformer;
5
+ const parser_1 = require("../parser");
4
6
  exports.indexContext = {
5
7
  multipleRenderRoots: false,
6
8
  nestedRender: false,
7
9
  };
8
- function transformer(file, api, options) {
9
- const j = api.jscodeshift.withParser('tsx');
10
+ function transformer(file, _api, options) {
11
+ const j = (0, parser_1.createParserFromPath)(file.path);
10
12
  const root = j(file.source);
11
13
  let hasModifications = false;
12
14
  let foundReactRender = 0;
@@ -28,7 +30,6 @@ function transformer(file, api, options) {
28
30
  root
29
31
  .find(j.CallExpression)
30
32
  .filter((path) => {
31
- var _a, _b;
32
33
  const { node } = path;
33
34
  let found = false;
34
35
  if (defaultReactDomImport &&
@@ -43,7 +44,7 @@ function transformer(file, api, options) {
43
44
  if (found) {
44
45
  foundReactRender++;
45
46
  hasModifications = true;
46
- if (!Array.isArray((_b = (_a = path.parentPath) === null || _a === void 0 ? void 0 : _a.parentPath) === null || _b === void 0 ? void 0 : _b.value)) {
47
+ if (!Array.isArray(path.parentPath?.parentPath?.value)) {
47
48
  exports.indexContext.nestedRender = true;
48
49
  return false;
49
50
  }
@@ -73,5 +74,4 @@ function transformer(file, api, options) {
73
74
  // }).remove()
74
75
  return hasModifications ? root.toSource(options) : null;
75
76
  }
76
- exports.default = transformer;
77
77
  //# sourceMappingURL=index-to-component.js.map
@@ -0,0 +1,63 @@
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.getPkgManager = getPkgManager;
7
+ exports.uninstallPackage = uninstallPackage;
8
+ exports.installPackages = installPackages;
9
+ const find_up_1 = __importDefault(require("find-up"));
10
+ const execa_1 = __importDefault(require("execa"));
11
+ const node_path_1 = require("node:path");
12
+ function getPkgManager(baseDir) {
13
+ try {
14
+ const lockFile = find_up_1.default.sync(['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb'], { cwd: baseDir });
15
+ if (lockFile) {
16
+ switch ((0, node_path_1.basename)(lockFile)) {
17
+ case 'package-lock.json':
18
+ return 'npm';
19
+ case 'yarn.lock':
20
+ return 'yarn';
21
+ case 'pnpm-lock.yaml':
22
+ return 'pnpm';
23
+ case 'bun.lockb':
24
+ return 'bun';
25
+ default:
26
+ return 'npm';
27
+ }
28
+ }
29
+ }
30
+ catch {
31
+ return 'npm';
32
+ }
33
+ }
34
+ function uninstallPackage(packageToUninstall, pkgManager) {
35
+ pkgManager ??= getPkgManager(process.cwd());
36
+ if (!pkgManager)
37
+ throw new Error('Failed to find package manager');
38
+ let command = 'uninstall';
39
+ if (pkgManager === 'yarn') {
40
+ command = 'remove';
41
+ }
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
+ }
48
+ }
49
+ function installPackages(packageToInstall, options = {}) {
50
+ const { packageManager = getPkgManager(process.cwd()), silent = false } = options;
51
+ if (!packageManager)
52
+ throw new Error('Failed to find package manager');
53
+ try {
54
+ execa_1.default.sync(packageManager, ['add', ...packageToInstall], {
55
+ // Keeping stderr since it'll likely be relevant later when it fails.
56
+ stdio: silent ? ['ignore', 'ignore', 'inherit'] : 'inherit',
57
+ });
58
+ }
59
+ catch (error) {
60
+ throw new Error(`Failed to install "${packageToInstall}". Please install it manually.`, { cause: error });
61
+ }
62
+ }
63
+ //# sourceMappingURL=handle-package.js.map
package/lib/install.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.install = void 0;
6
+ exports.install = install;
7
7
  /* eslint-disable import/no-extraneous-dependencies */
8
8
  const picocolors_1 = require("picocolors");
9
9
  const cross_spawn_1 = __importDefault(require("cross-spawn"));
@@ -84,7 +84,7 @@ function install(root, dependencies, { useYarn, isOnline, devDependencies }) {
84
84
  */
85
85
  const child = (0, cross_spawn_1.default)(command, args, {
86
86
  stdio: 'inherit',
87
- env: Object.assign(Object.assign({}, process.env), { ADBLOCK: '1', DISABLE_OPENCOLLECTIVE: '1' }),
87
+ env: { ...process.env, ADBLOCK: '1', DISABLE_OPENCOLLECTIVE: '1' },
88
88
  });
89
89
  child.on('close', (code) => {
90
90
  if (code !== 0) {
@@ -95,5 +95,4 @@ function install(root, dependencies, { useYarn, isOnline, devDependencies }) {
95
95
  });
96
96
  });
97
97
  }
98
- exports.install = install;
99
98
  //# sourceMappingURL=install.js.map
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