@next/codemod 15.0.0-canary.173 → 15.0.0-canary.175

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.
@@ -30,7 +30,12 @@ const program = new commander_1.Command(packageJson.name)
30
30
  program
31
31
  .command('upgrade')
32
32
  .description('Upgrade Next.js apps to desired versions with a single command.')
33
- .usage('[options]')
33
+ .argument('[revision]', 'NPM dist tag or exact version to upgrade to (e.g. "latest" or "15.0.0-canary.167"). Valid dist-tags are "latest", "canary" or "rc".', packageJson.version.includes('-canary.')
34
+ ? 'canary'
35
+ : packageJson.version.includes('-rc.')
36
+ ? 'rc'
37
+ : 'latest')
38
+ .option('--verbose', 'Verbose output', false)
34
39
  .action(upgrade_1.runUpgrade);
35
40
  program.parse(process.argv);
36
41
  //# sourceMappingURL=next-codemod.js.map
package/bin/transform.js CHANGED
@@ -98,7 +98,7 @@ async function runTransform(transform, path, options) {
98
98
  }
99
99
  if (!dry && transformer === 'next-request-geo-ip') {
100
100
  console.log('Installing `@vercel/functions`...');
101
- (0, handle_package_1.installPackage)('@vercel/functions');
101
+ (0, handle_package_1.installPackages)(['@vercel/functions']);
102
102
  }
103
103
  }
104
104
  //# sourceMappingURL=transform.js.map
package/bin/upgrade.js CHANGED
@@ -12,93 +12,73 @@ const compare_versions_1 = require("compare-versions");
12
12
  const chalk_1 = __importDefault(require("chalk"));
13
13
  const codemods_1 = require("../lib/codemods");
14
14
  const handle_package_1 = require("../lib/handle-package");
15
- async function runUpgrade() {
15
+ const transform_1 = require("./transform");
16
+ /**
17
+ * @param query
18
+ * @example loadHighestNPMVersionMatching("react@^18.3.0 || ^19.0.0") === Promise<"19.0.0">
19
+ */
20
+ async function loadHighestNPMVersionMatching(query) {
21
+ const versionsJSON = (0, child_process_1.execSync)(`npm --silent view "${query}" --json --field version`, { encoding: 'utf-8' });
22
+ const versions = JSON.parse(versionsJSON);
23
+ if (versions.length < 1) {
24
+ throw new Error(`Found no React versions matching "${query}". This is a bug in the upgrade tool.`);
25
+ }
26
+ return versions[versions.length - 1];
27
+ }
28
+ async function runUpgrade(revision, options) {
29
+ const { verbose } = options;
16
30
  const appPackageJsonPath = path_1.default.resolve(process.cwd(), 'package.json');
17
31
  let appPackageJson = JSON.parse(fs_1.default.readFileSync(appPackageJsonPath, 'utf8'));
18
32
  await detectWorkspace(appPackageJson);
19
33
  let targetNextPackageJson;
20
- let targetVersionSpecifier = '';
21
- const shortcutVersion = process.argv[2]?.replace('@', '');
22
- if (shortcutVersion) {
23
- const res = await fetch(`https://registry.npmjs.org/next/${shortcutVersion}`);
24
- if (res.status === 200) {
25
- targetNextPackageJson = await res.json();
26
- targetVersionSpecifier = targetNextPackageJson.version;
27
- }
28
- else {
29
- console.error(`${chalk_1.default.yellow('Next.js ' + shortcutVersion)} does not exist. Check available versions at ${chalk_1.default.underline('https://www.npmjs.com/package/next?activeTab=versions')}, or choose one from below\n`);
30
- }
34
+ const res = await fetch(`https://registry.npmjs.org/next/${revision}`);
35
+ if (res.status === 200) {
36
+ targetNextPackageJson = await res.json();
31
37
  }
32
- const installedNextVersion = await getInstalledNextVersion();
33
- if (!targetNextPackageJson) {
34
- let nextPackageJson = {};
35
- try {
36
- const resCanary = await fetch(`https://registry.npmjs.org/next/canary`);
37
- nextPackageJson['canary'] = await resCanary.json();
38
- const resRc = await fetch(`https://registry.npmjs.org/next/rc`);
39
- nextPackageJson['rc'] = await resRc.json();
40
- const resLatest = await fetch(`https://registry.npmjs.org/next/latest`);
41
- nextPackageJson['latest'] = await resLatest.json();
42
- }
43
- catch (error) {
44
- console.error('Failed to fetch versions from npm registry.');
45
- return;
46
- }
47
- let showRc = true;
48
- if (nextPackageJson['latest'].version && nextPackageJson['rc'].version) {
49
- showRc =
50
- (0, compare_versions_1.compareVersions)(nextPackageJson['rc'].version, nextPackageJson['latest'].version) === 1;
51
- }
52
- const choices = [
53
- {
54
- title: 'Canary',
55
- value: 'canary',
56
- description: `Experimental version with latest features (${nextPackageJson['canary'].version})`,
57
- },
58
- ];
59
- if (showRc) {
60
- choices.push({
61
- title: 'Release Candidate',
62
- value: 'rc',
63
- description: `Pre-release version for final testing (${nextPackageJson['rc'].version})`,
64
- });
65
- }
66
- choices.push({
67
- title: 'Stable',
68
- value: 'latest',
69
- description: `Production-ready release (${nextPackageJson['latest'].version})`,
70
- });
71
- if (installedNextVersion) {
72
- console.log(`You are currently using ${chalk_1.default.blue('Next.js ' + installedNextVersion)}`);
73
- }
74
- const initialVersionSpecifierIdx = await getVersionSpecifierIdx(installedNextVersion, showRc);
75
- const response = await (0, prompts_1.default)({
76
- type: 'select',
77
- name: 'version',
78
- message: 'What Next.js version do you want to upgrade to?',
79
- choices: choices,
80
- initial: initialVersionSpecifierIdx,
81
- }, { onCancel: () => process.exit(0) });
82
- targetNextPackageJson = nextPackageJson[response.version];
83
- targetVersionSpecifier = response.version;
38
+ else {
39
+ throw new Error(`${chalk_1.default.yellow(`next@${revision}`)} does not exist. Check available versions at ${chalk_1.default.underline('https://www.npmjs.com/package/next?activeTab=versions')}.`);
84
40
  }
41
+ const installedNextVersion = await getInstalledNextVersion();
85
42
  const targetNextVersion = targetNextPackageJson.version;
86
- if (targetNextVersion &&
87
- (0, compare_versions_1.compareVersions)(targetNextVersion, '15.0.0-canary') >= 0) {
43
+ // We're resolving a specific version here to avoid including "ugly" version queries
44
+ // in the manifest.
45
+ // E.g. in peerDependencies we could have `^18.2.0 || ^19.0.0 || 20.0.0-canary`
46
+ // If we'd just `npm add` that, the manifest would read the same version query.
47
+ // This is basically a `npm --save-exact react@$versionQuery` that works for every package manager.
48
+ const [targetReactVersion, targetReactTypesVersion, targetReactDOMTypesVersion,] = await Promise.all([
49
+ loadHighestNPMVersionMatching(`react@${targetNextPackageJson.peerDependencies['react']}`),
50
+ loadHighestNPMVersionMatching(`@types/react@${targetNextPackageJson.peerDependencies['react']}`),
51
+ loadHighestNPMVersionMatching(`@types/react-dom@${targetNextPackageJson.peerDependencies['react']}`),
52
+ ]);
53
+ if ((0, compare_versions_1.compareVersions)(targetNextVersion, '15.0.0-canary') >= 0) {
88
54
  await suggestTurbopack(appPackageJson);
89
55
  }
56
+ const codemods = await suggestCodemods(installedNextVersion, targetNextVersion);
90
57
  fs_1.default.writeFileSync(appPackageJsonPath, JSON.stringify(appPackageJson, null, 2));
91
58
  const packageManager = (0, handle_package_1.getPkgManager)(process.cwd());
92
59
  const nextDependency = `next@${targetNextVersion}`;
93
60
  const reactDependencies = [
94
- `react@${targetNextPackageJson.peerDependencies['react']}`,
95
- `@types/react@${targetNextPackageJson.devDependencies['@types/react']}`,
96
- `react-dom@${targetNextPackageJson.peerDependencies['react-dom']}`,
97
- `@types/react-dom@${targetNextPackageJson.devDependencies['@types/react-dom']}`,
61
+ `react@${targetReactVersion}`,
62
+ `react-dom@${targetReactVersion}`,
98
63
  ];
99
- (0, handle_package_1.installPackage)([nextDependency, ...reactDependencies], packageManager);
100
- console.log(`Upgrading your project to ${chalk_1.default.blue('Next.js ' + targetVersionSpecifier)}...\n`);
101
- await suggestCodemods(installedNextVersion, targetNextVersion);
64
+ if (targetReactVersion.startsWith('19.0.0-canary') ||
65
+ targetReactVersion.startsWith('19.0.0-beta') ||
66
+ targetReactVersion.startsWith('19.0.0-rc')) {
67
+ reactDependencies.push(`@types/react@npm:types-react@rc`);
68
+ reactDependencies.push(`@types/react-dom@npm:types-react-dom@rc`);
69
+ }
70
+ else {
71
+ reactDependencies.push(`@types/react@${targetReactTypesVersion}`);
72
+ reactDependencies.push(`@types/react-dom@${targetReactDOMTypesVersion}`);
73
+ }
74
+ console.log(`Upgrading your project to ${chalk_1.default.blue('Next.js ' + targetNextVersion)}...\n`);
75
+ (0, handle_package_1.installPackages)([nextDependency, ...reactDependencies], {
76
+ packageManager,
77
+ silent: !verbose,
78
+ });
79
+ for (const codemod of codemods) {
80
+ await (0, transform_1.runTransform)(codemod, process.cwd(), { force: true });
81
+ }
102
82
  console.log(`\n${chalk_1.default.green('✔')} Your Next.js project has been upgraded successfully. ${chalk_1.default.bold('Time to ship! 🚢')}`);
103
83
  }
104
84
  async function detectWorkspace(appPackageJson) {
@@ -124,22 +104,6 @@ async function getInstalledNextVersion() {
124
104
  const installedNextPackageJson = JSON.parse(fs_1.default.readFileSync(installedNextPackageJsonDir, 'utf8'));
125
105
  return installedNextPackageJson.version;
126
106
  }
127
- /*
128
- * Returns the index of the current version's specifier in the
129
- * array ['canary', 'rc', 'latest'] or ['canary', 'latest']
130
- */
131
- async function getVersionSpecifierIdx(installedNextVersion, showRc) {
132
- if (installedNextVersion == null) {
133
- return 0;
134
- }
135
- if (installedNextVersion.includes('canary')) {
136
- return 0;
137
- }
138
- if (installedNextVersion.includes('rc')) {
139
- return 1;
140
- }
141
- return showRc ? 2 : 1;
142
- }
143
107
  /*
144
108
  * Heuristics are used to determine whether to Turbopack is enabled or not and
145
109
  * to determine how to update the dev script.
@@ -184,7 +148,7 @@ async function suggestTurbopack(packageJson) {
184
148
  async function suggestCodemods(initialNextVersion, targetNextVersion) {
185
149
  const initialVersionIndex = codemods_1.availableCodemods.findIndex((versionCodemods) => (0, compare_versions_1.compareVersions)(versionCodemods.version, initialNextVersion) > 0);
186
150
  if (initialVersionIndex === -1) {
187
- return;
151
+ return [];
188
152
  }
189
153
  let targetVersionIndex = codemods_1.availableCodemods.findIndex((versionCodemods) => (0, compare_versions_1.compareVersions)(versionCodemods.version, targetNextVersion) > 0);
190
154
  if (targetVersionIndex === -1) {
@@ -194,31 +158,24 @@ async function suggestCodemods(initialNextVersion, targetNextVersion) {
194
158
  .slice(initialVersionIndex, targetVersionIndex)
195
159
  .flatMap((versionCodemods) => versionCodemods.codemods);
196
160
  if (relevantCodemods.length === 0) {
197
- return;
161
+ return [];
198
162
  }
199
- let codemodsString = `\nThe following ${chalk_1.default.blue('codemods')} are available for your upgrade:`;
200
- relevantCodemods.forEach((codemod) => {
201
- codemodsString += `\n- ${codemod.title} ${chalk_1.default.gray(`(${codemod.value})`)}`;
202
- });
203
- codemodsString += '\n';
204
- console.log(codemodsString);
205
- const responseCodemods = await (0, prompts_1.default)({
206
- type: 'confirm',
207
- name: 'apply',
208
- message: `Do you want to apply these codemods?`,
209
- initial: true,
163
+ const { codemods } = await (0, prompts_1.default)({
164
+ type: 'multiselect',
165
+ name: 'codemods',
166
+ message: `\nThe following ${chalk_1.default.blue('codemods')} are recommended for your upgrade. Would you like to apply them?`,
167
+ choices: relevantCodemods.map((codemod) => {
168
+ return {
169
+ title: `${codemod.title} ${chalk_1.default.grey(`(${codemod.value})`)}`,
170
+ value: codemod.value,
171
+ selected: true,
172
+ };
173
+ }),
210
174
  }, {
211
175
  onCancel: () => {
212
176
  process.exit(0);
213
177
  },
214
178
  });
215
- if (!responseCodemods.apply) {
216
- return;
217
- }
218
- for (const codemod of relevantCodemods) {
219
- (0, child_process_1.execSync)(`npx @next/codemod@latest ${codemod.value} ${process.cwd()} --force`, {
220
- stdio: 'inherit',
221
- });
222
- }
179
+ return codemods;
223
180
  }
224
181
  //# sourceMappingURL=upgrade.js.map
package/lib/codemods.js CHANGED
@@ -87,17 +87,22 @@ exports.availableCodemods = [
87
87
  ],
88
88
  },
89
89
  {
90
- version: '15.0',
90
+ version: '15.0.0-canary.153',
91
91
  codemods: [
92
- {
93
- title: 'Transforms usage of Next.js async Request APIs',
94
- value: 'next-async-request-api',
95
- },
96
92
  {
97
93
  title: 'Migrate `geo` and `ip` properties on `NextRequest`',
98
94
  value: 'next-request-geo-ip',
99
95
  },
100
96
  ],
101
97
  },
98
+ {
99
+ version: '15.0.0-canary.171',
100
+ codemods: [
101
+ {
102
+ title: 'Transforms usage of Next.js async Request APIs',
103
+ value: 'next-async-request-api',
104
+ },
105
+ ],
106
+ },
102
107
  ];
103
108
  //# sourceMappingURL=codemods.js.map
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getPkgManager = getPkgManager;
7
7
  exports.uninstallPackage = uninstallPackage;
8
- exports.installPackage = installPackage;
8
+ exports.installPackages = installPackages;
9
9
  const fs_1 = __importDefault(require("fs"));
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const execa_1 = __importDefault(require("execa"));
@@ -59,15 +59,15 @@ function uninstallPackage(packageToUninstall, pkgManager) {
59
59
  }
60
60
  execa_1.default.sync(pkgManager, [command, packageToUninstall], { stdio: 'inherit' });
61
61
  }
62
- function installPackage(packageToInstall, pkgManager) {
63
- pkgManager ??= getPkgManager(process.cwd());
64
- if (!pkgManager)
62
+ function installPackages(packageToInstall, options = {}) {
63
+ const { packageManager = getPkgManager(process.cwd()), silent = false } = options;
64
+ if (!packageManager)
65
65
  throw new Error('Failed to find package manager');
66
- if (Array.isArray(packageToInstall)) {
67
- packageToInstall = packageToInstall.join(' ');
68
- }
69
66
  try {
70
- execa_1.default.sync(pkgManager, ['add', packageToInstall], { stdio: 'inherit' });
67
+ execa_1.default.sync(packageManager, ['add', ...packageToInstall], {
68
+ // Keeping stderr since it'll likely be relevant later when it fails.
69
+ stdio: silent ? ['ignore', 'ignore', 'inherit'] : 'inherit',
70
+ });
71
71
  }
72
72
  catch (error) {
73
73
  throw new Error(`Failed to install "${packageToInstall}". Please install it manually.`, { cause: error });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next/codemod",
3
- "version": "15.0.0-canary.173",
3
+ "version": "15.0.0-canary.175",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,7 +17,7 @@ function findDynamicImportsAndComment(root, j) {
17
17
  arguments: [{ value: 'next/headers' }],
18
18
  });
19
19
  importPaths.forEach((path) => {
20
- (0, utils_1.insertCommentOnce)(path, j, DYNAMIC_IMPORT_WARN_COMMENT);
20
+ (0, utils_1.insertCommentOnce)(path.node, j, DYNAMIC_IMPORT_WARN_COMMENT);
21
21
  modified = true;
22
22
  });
23
23
  return modified;
@@ -145,7 +145,7 @@ function transformDynamicAPI(source, api, filePath) {
145
145
  }
146
146
  });
147
147
  }
148
- const isClientComponent = (0, utils_1.determineClientDirective)(root, j, source);
148
+ const isClientComponent = (0, utils_1.determineClientDirective)(root, j);
149
149
  // Only transform the valid calls in server or shared components
150
150
  if (isClientComponent)
151
151
  return null;
@@ -219,10 +219,7 @@ function castTypesOrAddComment(j, path, originRequestApiName, root, filePath, in
219
219
  }
220
220
  else {
221
221
  // Otherwise for JS file, leave a message to the user to manually handle the transformation
222
- path.node.comments = [
223
- j.commentBlock(customMessage),
224
- ...(path.node.comments || []),
225
- ];
222
+ (0, utils_1.insertCommentOnce)(path.node, j, customMessage);
226
223
  }
227
224
  }
228
225
  function findImportMappingFromNextHeaders(root, j) {
@@ -89,7 +89,87 @@ function applyUseAndRenameAccessedProp(propIdName, path, j) {
89
89
  }
90
90
  return modified;
91
91
  }
92
- function transformDynamicProps(source, api, _filePath) {
92
+ const MATCHED_FILE_PATTERNS = /([\\/]|^)(page|layout)\.(t|j)sx?$/;
93
+ function modifyTypes(paramTypeAnnotation, propsIdentifier, root, j) {
94
+ if (paramTypeAnnotation && paramTypeAnnotation.typeAnnotation) {
95
+ const typeAnnotation = paramTypeAnnotation.typeAnnotation;
96
+ if (typeAnnotation.type === 'TSTypeLiteral') {
97
+ const typeLiteral = typeAnnotation;
98
+ // Find the type property for `params`
99
+ typeLiteral.members.forEach((member) => {
100
+ if (member.type === 'TSPropertySignature' &&
101
+ member.key.type === 'Identifier' &&
102
+ utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
103
+ // if it's already a Promise, don't wrap it again, return
104
+ if (member.typeAnnotation &&
105
+ member.typeAnnotation.typeAnnotation &&
106
+ member.typeAnnotation.typeAnnotation.type === 'TSTypeReference' &&
107
+ member.typeAnnotation.typeAnnotation.typeName.type ===
108
+ 'Identifier' &&
109
+ member.typeAnnotation.typeAnnotation.typeName.name === 'Promise') {
110
+ return;
111
+ }
112
+ // Wrap the `params` type in Promise<>
113
+ if (member.typeAnnotation &&
114
+ member.typeAnnotation.typeAnnotation &&
115
+ j.TSType.check(member.typeAnnotation.typeAnnotation)) {
116
+ member.typeAnnotation.typeAnnotation = j.tsTypeReference(j.identifier('Promise'), j.tsTypeParameterInstantiation([
117
+ // @ts-ignore
118
+ member.typeAnnotation.typeAnnotation,
119
+ ]));
120
+ }
121
+ }
122
+ });
123
+ }
124
+ else if (typeAnnotation.type === 'TSTypeReference') {
125
+ // If typeAnnotation is a type or interface, change the properties to Promise<type of property>
126
+ // e.g. interface PageProps { params: { slug: string } } => interface PageProps { params: Promise<{ slug: string }> }
127
+ const typeReference = typeAnnotation;
128
+ if (typeReference.typeName.type === 'Identifier') {
129
+ // Find the actual type of the type reference
130
+ const foundTypes = findAllTypes(root, j, typeReference.typeName.name);
131
+ // Deal with interfaces
132
+ if (foundTypes.interfaces.length > 0) {
133
+ const interfaceDeclaration = foundTypes.interfaces[0];
134
+ if (interfaceDeclaration.type === 'TSInterfaceDeclaration' &&
135
+ interfaceDeclaration.body?.type === 'TSInterfaceBody') {
136
+ const typeBody = interfaceDeclaration.body.body;
137
+ // if it's already a Promise, don't wrap it again, return
138
+ // traverse the typeReference's properties, if any is in propNames, wrap it in Promise<> if needed
139
+ typeBody.forEach((member) => {
140
+ if (member.type === 'TSPropertySignature' &&
141
+ member.key.type === 'Identifier' &&
142
+ utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
143
+ // if it's already a Promise, don't wrap it again, return
144
+ if (member.typeAnnotation &&
145
+ member.typeAnnotation.typeAnnotation &&
146
+ member.typeAnnotation?.typeAnnotation?.typeName?.name ===
147
+ 'Promise') {
148
+ return;
149
+ }
150
+ // Wrap the prop type in Promise<>
151
+ if (member.typeAnnotation &&
152
+ member.typeAnnotation.typeAnnotation &&
153
+ // check if member name is in propNames
154
+ utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
155
+ member.typeAnnotation.typeAnnotation = j.tsTypeReference(j.identifier('Promise'), j.tsTypeParameterInstantiation([
156
+ member.typeAnnotation.typeAnnotation,
157
+ ]));
158
+ }
159
+ }
160
+ });
161
+ }
162
+ }
163
+ }
164
+ }
165
+ propsIdentifier.typeAnnotation = paramTypeAnnotation;
166
+ }
167
+ }
168
+ function transformDynamicProps(source, api, filePath) {
169
+ const isMatched = MATCHED_FILE_PATTERNS.test(filePath);
170
+ if (!isMatched) {
171
+ return null;
172
+ }
93
173
  let modified = false;
94
174
  let modifiedPropArgument = false;
95
175
  const j = api.jscodeshift.withParser('tsx');
@@ -100,29 +180,39 @@ function transformDynamicProps(source, api, _filePath) {
100
180
  // e.g. destruct `params` { slug } = params
101
181
  // e.g. destruct `searchParams `{ search } = searchParams
102
182
  let insertedDestructPropNames = new Set();
103
- // Rename props to `prop` argument for the function
104
- let insertedRenamedPropFunctionNames = new Set();
105
183
  function processAsyncPropOfEntryFile(isClientComponent) {
106
184
  // find `params` and `searchParams` in file, and transform the access to them
107
185
  function renameAsyncPropIfExisted(path, isDefaultExport) {
108
186
  const decl = path.value;
109
187
  const params = decl.params;
110
- const functionName = decl.id?.name || 'default';
188
+ let functionName = decl.id?.name;
189
+ // If it's const <id> = function () {}, locate the <id> to get function name
190
+ if (!decl.id) {
191
+ functionName = (0, utils_1.getVariableDeclaratorId)(path, j)?.name;
192
+ }
111
193
  // target properties mapping, only contains `params` and `searchParams`
112
194
  const propertiesMap = new Map();
113
195
  let allProperties = [];
196
+ const isRoute = !isDefaultExport && utils_1.TARGET_ROUTE_EXPORTS.has(functionName);
114
197
  // generateMetadata API has 2 params
115
198
  if (functionName === 'generateMetadata') {
116
199
  if (params.length > 2 || params.length === 0)
117
200
  return;
118
201
  }
202
+ else if (isRoute) {
203
+ if (params.length !== 2)
204
+ return;
205
+ }
119
206
  else {
120
- // Page/Layout/Route handlers have 1 param
207
+ // Page/Layout default export have 1 param
121
208
  if (params.length !== 1)
122
209
  return;
123
210
  }
124
211
  const propsIdentifier = (0, utils_1.generateUniqueIdentifier)(PAGE_PROPS, path, j);
125
- const currentParam = params[0];
212
+ const propsArgumentIndex = isRoute ? 1 : 0;
213
+ const currentParam = params[propsArgumentIndex];
214
+ if (!currentParam)
215
+ return;
126
216
  // Argument destructuring case
127
217
  if (currentParam.type === 'ObjectPattern') {
128
218
  // Validate if the properties are not `params` and `searchParams`,
@@ -150,85 +240,9 @@ function transformDynamicProps(source, api, _filePath) {
150
240
  propertiesMap.set(prop.key.name, value);
151
241
  }
152
242
  });
153
- const paramTypeAnnotation = currentParam.typeAnnotation;
154
- if (paramTypeAnnotation && paramTypeAnnotation.typeAnnotation) {
155
- const typeAnnotation = paramTypeAnnotation.typeAnnotation;
156
- if (typeAnnotation.type === 'TSTypeLiteral') {
157
- const typeLiteral = typeAnnotation;
158
- // Find the type property for `params`
159
- typeLiteral.members.forEach((member) => {
160
- if (member.type === 'TSPropertySignature' &&
161
- member.key.type === 'Identifier' &&
162
- propertiesMap.has(member.key.name)) {
163
- // if it's already a Promise, don't wrap it again, return
164
- if (member.typeAnnotation &&
165
- member.typeAnnotation.typeAnnotation &&
166
- member.typeAnnotation.typeAnnotation.type ===
167
- 'TSTypeReference' &&
168
- member.typeAnnotation.typeAnnotation.typeName.type ===
169
- 'Identifier' &&
170
- member.typeAnnotation.typeAnnotation.typeName.name ===
171
- 'Promise') {
172
- return;
173
- }
174
- // Wrap the `params` type in Promise<>
175
- if (member.typeAnnotation &&
176
- member.typeAnnotation.typeAnnotation &&
177
- j.TSType.check(member.typeAnnotation.typeAnnotation)) {
178
- member.typeAnnotation.typeAnnotation = j.tsTypeReference(j.identifier('Promise'), j.tsTypeParameterInstantiation([
179
- // @ts-ignore
180
- member.typeAnnotation.typeAnnotation,
181
- ]));
182
- }
183
- }
184
- });
185
- }
186
- else if (typeAnnotation.type === 'TSTypeReference') {
187
- // If typeAnnotation is a type or interface, change the properties to Promise<type of property>
188
- // e.g. interface PageProps { params: { slug: string } } => interface PageProps { params: Promise<{ slug: string }> }
189
- const typeReference = typeAnnotation;
190
- if (typeReference.typeName.type === 'Identifier') {
191
- // Find the actual type of the type reference
192
- const foundTypes = findAllTypes(root, j, typeReference.typeName.name);
193
- // Deal with interfaces
194
- if (foundTypes.interfaces.length > 0) {
195
- const interfaceDeclaration = foundTypes.interfaces[0];
196
- if (interfaceDeclaration.type === 'TSInterfaceDeclaration' &&
197
- interfaceDeclaration.body?.type === 'TSInterfaceBody') {
198
- const typeBody = interfaceDeclaration.body.body;
199
- // if it's already a Promise, don't wrap it again, return
200
- // traverse the typeReference's properties, if any is in propNames, wrap it in Promise<> if needed
201
- typeBody.forEach((member) => {
202
- if (member.type === 'TSPropertySignature' &&
203
- member.key.type === 'Identifier' &&
204
- utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
205
- // if it's already a Promise, don't wrap it again, return
206
- if (member.typeAnnotation &&
207
- member.typeAnnotation.typeAnnotation &&
208
- member.typeAnnotation?.typeAnnotation?.typeName
209
- ?.name === 'Promise') {
210
- return;
211
- }
212
- // Wrap the prop type in Promise<>
213
- if (member.typeAnnotation &&
214
- member.typeAnnotation.typeAnnotation &&
215
- // check if member name is in propNames
216
- utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
217
- member.typeAnnotation.typeAnnotation =
218
- j.tsTypeReference(j.identifier('Promise'), j.tsTypeParameterInstantiation([
219
- member.typeAnnotation.typeAnnotation,
220
- ]));
221
- }
222
- }
223
- });
224
- }
225
- }
226
- }
227
- }
228
- propsIdentifier.typeAnnotation = paramTypeAnnotation;
229
- }
243
+ modifyTypes(currentParam.typeAnnotation, propsIdentifier, root, j);
230
244
  // Override the first param to `props`
231
- params[0] = propsIdentifier;
245
+ params[propsArgumentIndex] = propsIdentifier;
232
246
  modified = true;
233
247
  modifiedPropArgument = true;
234
248
  }
@@ -264,14 +278,13 @@ function transformDynamicProps(source, api, _filePath) {
264
278
  // find the argument `currentParam`
265
279
  const args = callExpression.value.arguments;
266
280
  const propPassedAsArg = args.find((arg) => j.Identifier.check(arg) && arg.name === argName);
267
- // insert a comment to the argument
268
- const comment = j.commentBlock(` '${argName}' is passed as an argument. Any asynchronous properties of 'props' must be awaited when accessed. `, true, false);
269
- propPassedAsArg.comments = [
270
- comment,
271
- ...(propPassedAsArg.comments || []),
272
- ];
281
+ const comment = ` '${argName}' is passed as an argument. Any asynchronous properties of 'props' must be awaited when accessed. `;
282
+ (0, utils_1.insertCommentOnce)(propPassedAsArg, j, comment);
273
283
  modified = true;
274
284
  });
285
+ if (modified) {
286
+ modifyTypes(currentParam.typeAnnotation, propsIdentifier, root, j);
287
+ }
275
288
  }
276
289
  if (modifiedPropArgument) {
277
290
  resolveAsyncProp(path, propertiesMap, propsIdentifier.name, allProperties, isDefaultExport);
@@ -279,6 +292,8 @@ function transformDynamicProps(source, api, _filePath) {
279
292
  }
280
293
  // Helper function to insert `const params = await asyncParams;` at the beginning of the function body
281
294
  function resolveAsyncProp(path, propertiesMap, propsIdentifierName, allProperties, isDefaultExport) {
295
+ // Rename props to `prop` argument for the function
296
+ const insertedRenamedPropFunctionNames = new Set();
282
297
  const node = path.value;
283
298
  // If it's sync default export, and it's also server component, make the function async
284
299
  if (isDefaultExport && !isClientComponent) {
@@ -289,6 +304,27 @@ function transformDynamicProps(source, api, _filePath) {
289
304
  }
290
305
  }
291
306
  }
307
+ // If it's arrow function and function body is not block statement, check if the properties are used there
308
+ if (j.ArrowFunctionExpression.check(path.node) &&
309
+ !j.BlockStatement.check(path.node.body)) {
310
+ const objectExpression = path.node.body;
311
+ let hasUsedProps = false;
312
+ j(objectExpression)
313
+ .find(j.Identifier)
314
+ .forEach((identifierPath) => {
315
+ const idName = identifierPath.value.name;
316
+ if (propertiesMap.has(idName)) {
317
+ hasUsedProps = true;
318
+ return;
319
+ }
320
+ });
321
+ // Turn the function body to block statement, return the object expression
322
+ if (hasUsedProps) {
323
+ path.node.body = j.blockStatement([
324
+ j.returnStatement(objectExpression),
325
+ ]);
326
+ }
327
+ }
292
328
  const isAsyncFunc = !!node.async;
293
329
  const functionName = path.value.id?.name || 'default';
294
330
  const functionBody = findFunctionBody(path);
@@ -448,7 +484,7 @@ function transformDynamicProps(source, api, _filePath) {
448
484
  }
449
485
  });
450
486
  }
451
- const isClientComponent = (0, utils_1.determineClientDirective)(root, j, source);
487
+ const isClientComponent = (0, utils_1.determineClientDirective)(root, j);
452
488
  // Apply to `params` and `searchParams`
453
489
  processAsyncPropOfEntryFile(isClientComponent);
454
490
  // Add import { use } from 'react' if needed and not already imported
@@ -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 = void 0;
3
+ exports.TARGET_PROP_NAMES = exports.TARGET_NAMED_EXPORTS = exports.TARGET_ROUTE_EXPORTS = void 0;
4
4
  exports.isFunctionType = isFunctionType;
5
5
  exports.isMatchedFunctionExported = isMatchedFunctionExported;
6
6
  exports.determineClientDirective = determineClientDirective;
@@ -13,17 +13,20 @@ exports.findClosetParentFunctionScope = findClosetParentFunctionScope;
13
13
  exports.getFunctionPathFromExportPath = getFunctionPathFromExportPath;
14
14
  exports.wrapParentheseIfNeeded = wrapParentheseIfNeeded;
15
15
  exports.insertCommentOnce = insertCommentOnce;
16
- exports.TARGET_NAMED_EXPORTS = new Set([
17
- // For custom route
16
+ exports.getVariableDeclaratorId = getVariableDeclaratorId;
17
+ exports.TARGET_ROUTE_EXPORTS = new Set([
18
18
  'GET',
19
- 'HEAD',
20
19
  'POST',
21
20
  'PUT',
22
- 'DELETE',
23
21
  'PATCH',
22
+ 'DELETE',
24
23
  'OPTIONS',
24
+ 'HEAD',
25
+ ]);
26
+ exports.TARGET_NAMED_EXPORTS = new Set([
25
27
  // For page and layout
26
28
  'generateMetadata',
29
+ ...exports.TARGET_ROUTE_EXPORTS,
27
30
  ]);
28
31
  exports.TARGET_PROP_NAMES = new Set(['params', 'searchParams']);
29
32
  function isFunctionType(type) {
@@ -87,31 +90,15 @@ function isMatchedFunctionExported(path, j) {
87
90
  return true;
88
91
  return isNamedExport;
89
92
  }
90
- function determineClientDirective(root, j, source) {
91
- const hasStringDirective = root
92
- .find(j.Literal)
93
- .filter((path) => {
94
- const expr = path.node;
95
- return (expr.value === 'use client' && path.parentPath.node.type === 'Program');
96
- })
97
- .size() > 0;
98
- // 'use client';
99
- const hasStringDirectiveWithSemicolon = root
100
- .find(j.StringLiteral)
101
- .filter((path) => {
102
- const expr = path.node;
103
- return (expr.type === 'StringLiteral' &&
104
- expr.value === 'use client' &&
105
- path.parentPath.node.type === 'Program');
106
- })
107
- .size() > 0;
108
- if (hasStringDirective || hasStringDirectiveWithSemicolon)
109
- return true;
110
- // Since the client detection is not reliable with AST in jscodeshift,
111
- // determine if 'use client' or "use client" is leading in the source code.
112
- const trimmedSource = source.trim();
113
- const containsClientDirective = /^'use client'/.test(trimmedSource) || /^"use client"/g.test(trimmedSource);
114
- return containsClientDirective;
93
+ // directive is not parsed into AST, so we need to manually find it
94
+ // by going through the tokens. Use the 1st string token as the directive
95
+ function determineClientDirective(root, j) {
96
+ const { program } = root.get().node;
97
+ const directive = program.directives[0];
98
+ if (j.Directive.check(directive)) {
99
+ return directive.value.value === 'use client';
100
+ }
101
+ return false;
115
102
  }
116
103
  function isPromiseType(typeAnnotation) {
117
104
  return (typeAnnotation.type === 'TSTypeReference' &&
@@ -338,13 +325,23 @@ function getFunctionPathFromExportPath(exportPath, j, root, namedExportFilter) {
338
325
  function wrapParentheseIfNeeded(hasChainAccess, j, expression) {
339
326
  return hasChainAccess ? j.parenthesizedExpression(expression) : expression;
340
327
  }
341
- function insertCommentOnce(path, j, comment) {
342
- if (path.node.comments) {
343
- const hasComment = path.node.comments.some((commentNode) => commentNode.value === comment);
328
+ function insertCommentOnce(node, j, comment) {
329
+ if (node.comments) {
330
+ const hasComment = node.comments.some((commentNode) => commentNode.value === comment);
344
331
  if (hasComment) {
345
332
  return;
346
333
  }
347
334
  }
348
- path.node.comments = [j.commentBlock(comment), ...(path.node.comments || [])];
335
+ node.comments = [j.commentBlock(comment), ...(node.comments || [])];
336
+ }
337
+ function getVariableDeclaratorId(path, j) {
338
+ const parent = path.parentPath;
339
+ if (j.VariableDeclarator.check(parent.node)) {
340
+ const id = parent.node.id;
341
+ if (j.Identifier.check(id)) {
342
+ return id;
343
+ }
344
+ }
345
+ return undefined;
349
346
  }
350
347
  //# sourceMappingURL=utils.js.map