@next/codemod 15.0.0-canary.174 → 15.0.0-canary.176
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/next-codemod.js
CHANGED
|
@@ -30,7 +30,11 @@ 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
|
-
.argument('[revision]', 'NPM dist tag or exact version to upgrade to (e.g. "latest" or "15.0.0-canary.167").
|
|
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')
|
|
34
38
|
.option('--verbose', 'Verbose output', false)
|
|
35
39
|
.action(upgrade_1.runUpgrade);
|
|
36
40
|
program.parse(process.argv);
|
package/bin/upgrade.js
CHANGED
|
@@ -31,70 +31,14 @@ async function runUpgrade(revision, options) {
|
|
|
31
31
|
let appPackageJson = JSON.parse(fs_1.default.readFileSync(appPackageJsonPath, 'utf8'));
|
|
32
32
|
await detectWorkspace(appPackageJson);
|
|
33
33
|
let targetNextPackageJson;
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
if (res.status === 200) {
|
|
38
|
-
targetNextPackageJson = await res.json();
|
|
39
|
-
targetVersionSpecifier = targetNextPackageJson.version;
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
console.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')}, or choose one from below\n`);
|
|
43
|
-
}
|
|
34
|
+
const res = await fetch(`https://registry.npmjs.org/next/${revision}`);
|
|
35
|
+
if (res.status === 200) {
|
|
36
|
+
targetNextPackageJson = await res.json();
|
|
44
37
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let nextPackageJson = {};
|
|
48
|
-
try {
|
|
49
|
-
const resCanary = await fetch(`https://registry.npmjs.org/next/canary`);
|
|
50
|
-
nextPackageJson['canary'] = await resCanary.json();
|
|
51
|
-
const resRc = await fetch(`https://registry.npmjs.org/next/rc`);
|
|
52
|
-
nextPackageJson['rc'] = await resRc.json();
|
|
53
|
-
const resLatest = await fetch(`https://registry.npmjs.org/next/latest`);
|
|
54
|
-
nextPackageJson['latest'] = await resLatest.json();
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
console.error('Failed to fetch versions from npm registry.');
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
let showRc = true;
|
|
61
|
-
if (nextPackageJson['latest'].version && nextPackageJson['rc'].version) {
|
|
62
|
-
showRc =
|
|
63
|
-
(0, compare_versions_1.compareVersions)(nextPackageJson['rc'].version, nextPackageJson['latest'].version) === 1;
|
|
64
|
-
}
|
|
65
|
-
const choices = [
|
|
66
|
-
{
|
|
67
|
-
title: 'Canary',
|
|
68
|
-
value: 'canary',
|
|
69
|
-
description: `Experimental version with latest features (${nextPackageJson['canary'].version})`,
|
|
70
|
-
},
|
|
71
|
-
];
|
|
72
|
-
if (showRc) {
|
|
73
|
-
choices.push({
|
|
74
|
-
title: 'Release Candidate',
|
|
75
|
-
value: 'rc',
|
|
76
|
-
description: `Pre-release version for final testing (${nextPackageJson['rc'].version})`,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
choices.push({
|
|
80
|
-
title: 'Stable',
|
|
81
|
-
value: 'latest',
|
|
82
|
-
description: `Production-ready release (${nextPackageJson['latest'].version})`,
|
|
83
|
-
});
|
|
84
|
-
if (installedNextVersion) {
|
|
85
|
-
console.log(`You are currently using ${chalk_1.default.blue('Next.js ' + installedNextVersion)}`);
|
|
86
|
-
}
|
|
87
|
-
const initialVersionSpecifierIdx = await getVersionSpecifierIdx(installedNextVersion, showRc);
|
|
88
|
-
const response = await (0, prompts_1.default)({
|
|
89
|
-
type: 'select',
|
|
90
|
-
name: 'version',
|
|
91
|
-
message: 'What Next.js version do you want to upgrade to?',
|
|
92
|
-
choices: choices,
|
|
93
|
-
initial: initialVersionSpecifierIdx,
|
|
94
|
-
}, { onCancel: () => process.exit(0) });
|
|
95
|
-
targetNextPackageJson = nextPackageJson[response.version];
|
|
96
|
-
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')}.`);
|
|
97
40
|
}
|
|
41
|
+
const installedNextVersion = await getInstalledNextVersion();
|
|
98
42
|
const targetNextVersion = targetNextPackageJson.version;
|
|
99
43
|
// We're resolving a specific version here to avoid including "ugly" version queries
|
|
100
44
|
// in the manifest.
|
|
@@ -109,6 +53,7 @@ async function runUpgrade(revision, options) {
|
|
|
109
53
|
if ((0, compare_versions_1.compareVersions)(targetNextVersion, '15.0.0-canary') >= 0) {
|
|
110
54
|
await suggestTurbopack(appPackageJson);
|
|
111
55
|
}
|
|
56
|
+
const codemods = await suggestCodemods(installedNextVersion, targetNextVersion);
|
|
112
57
|
fs_1.default.writeFileSync(appPackageJsonPath, JSON.stringify(appPackageJson, null, 2));
|
|
113
58
|
const packageManager = (0, handle_package_1.getPkgManager)(process.cwd());
|
|
114
59
|
const nextDependency = `next@${targetNextVersion}`;
|
|
@@ -126,12 +71,14 @@ async function runUpgrade(revision, options) {
|
|
|
126
71
|
reactDependencies.push(`@types/react@${targetReactTypesVersion}`);
|
|
127
72
|
reactDependencies.push(`@types/react-dom@${targetReactDOMTypesVersion}`);
|
|
128
73
|
}
|
|
129
|
-
console.log(`Upgrading your project to ${chalk_1.default.blue('Next.js ' +
|
|
74
|
+
console.log(`Upgrading your project to ${chalk_1.default.blue('Next.js ' + targetNextVersion)}...\n`);
|
|
130
75
|
(0, handle_package_1.installPackages)([nextDependency, ...reactDependencies], {
|
|
131
76
|
packageManager,
|
|
132
77
|
silent: !verbose,
|
|
133
78
|
});
|
|
134
|
-
|
|
79
|
+
for (const codemod of codemods) {
|
|
80
|
+
await (0, transform_1.runTransform)(codemod, process.cwd(), { force: true });
|
|
81
|
+
}
|
|
135
82
|
console.log(`\n${chalk_1.default.green('✔')} Your Next.js project has been upgraded successfully. ${chalk_1.default.bold('Time to ship! 🚢')}`);
|
|
136
83
|
}
|
|
137
84
|
async function detectWorkspace(appPackageJson) {
|
|
@@ -157,22 +104,6 @@ async function getInstalledNextVersion() {
|
|
|
157
104
|
const installedNextPackageJson = JSON.parse(fs_1.default.readFileSync(installedNextPackageJsonDir, 'utf8'));
|
|
158
105
|
return installedNextPackageJson.version;
|
|
159
106
|
}
|
|
160
|
-
/*
|
|
161
|
-
* Returns the index of the current version's specifier in the
|
|
162
|
-
* array ['canary', 'rc', 'latest'] or ['canary', 'latest']
|
|
163
|
-
*/
|
|
164
|
-
async function getVersionSpecifierIdx(installedNextVersion, showRc) {
|
|
165
|
-
if (installedNextVersion == null) {
|
|
166
|
-
return 0;
|
|
167
|
-
}
|
|
168
|
-
if (installedNextVersion.includes('canary')) {
|
|
169
|
-
return 0;
|
|
170
|
-
}
|
|
171
|
-
if (installedNextVersion.includes('rc')) {
|
|
172
|
-
return 1;
|
|
173
|
-
}
|
|
174
|
-
return showRc ? 2 : 1;
|
|
175
|
-
}
|
|
176
107
|
/*
|
|
177
108
|
* Heuristics are used to determine whether to Turbopack is enabled or not and
|
|
178
109
|
* to determine how to update the dev script.
|
|
@@ -217,7 +148,7 @@ async function suggestTurbopack(packageJson) {
|
|
|
217
148
|
async function suggestCodemods(initialNextVersion, targetNextVersion) {
|
|
218
149
|
const initialVersionIndex = codemods_1.availableCodemods.findIndex((versionCodemods) => (0, compare_versions_1.compareVersions)(versionCodemods.version, initialNextVersion) > 0);
|
|
219
150
|
if (initialVersionIndex === -1) {
|
|
220
|
-
return;
|
|
151
|
+
return [];
|
|
221
152
|
}
|
|
222
153
|
let targetVersionIndex = codemods_1.availableCodemods.findIndex((versionCodemods) => (0, compare_versions_1.compareVersions)(versionCodemods.version, targetNextVersion) > 0);
|
|
223
154
|
if (targetVersionIndex === -1) {
|
|
@@ -227,7 +158,7 @@ async function suggestCodemods(initialNextVersion, targetNextVersion) {
|
|
|
227
158
|
.slice(initialVersionIndex, targetVersionIndex)
|
|
228
159
|
.flatMap((versionCodemods) => versionCodemods.codemods);
|
|
229
160
|
if (relevantCodemods.length === 0) {
|
|
230
|
-
return;
|
|
161
|
+
return [];
|
|
231
162
|
}
|
|
232
163
|
const { codemods } = await (0, prompts_1.default)({
|
|
233
164
|
type: 'multiselect',
|
|
@@ -245,8 +176,6 @@ async function suggestCodemods(initialNextVersion, targetNextVersion) {
|
|
|
245
176
|
process.exit(0);
|
|
246
177
|
},
|
|
247
178
|
});
|
|
248
|
-
|
|
249
|
-
await (0, transform_1.runTransform)(codemod, process.cwd(), { force: true });
|
|
250
|
-
}
|
|
179
|
+
return codemods;
|
|
251
180
|
}
|
|
252
181
|
//# sourceMappingURL=upgrade.js.map
|
package/package.json
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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[
|
|
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
|
-
|
|
268
|
-
|
|
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
|
|
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.
|
|
17
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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(
|
|
342
|
-
if (
|
|
343
|
-
const hasComment =
|
|
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
|
-
|
|
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
|