@next/codemod 15.0.0-canary.162 → 15.0.0-canary.164
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/cli.js +14 -2
- package/lib/{uninstall-package.js → handle-package.js} +13 -1
- package/package.json +2 -1
- package/transforms/lib/async-request-api/index.js +21 -0
- package/transforms/lib/async-request-api/next-async-dynamic-api.js +222 -0
- package/transforms/lib/async-request-api/next-async-dynamic-prop.js +347 -0
- package/transforms/lib/async-request-api/utils.js +198 -0
- package/transforms/next-async-request-api.js +9 -0
- package/transforms/next-request-geo-ip.js +334 -0
package/bin/cli.js
CHANGED
|
@@ -23,7 +23,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
23
23
|
const execa_1 = __importDefault(require("execa"));
|
|
24
24
|
const picocolors_1 = require("picocolors");
|
|
25
25
|
const is_git_clean_1 = __importDefault(require("is-git-clean"));
|
|
26
|
-
const
|
|
26
|
+
const handle_package_1 = require("../lib/handle-package");
|
|
27
27
|
exports.jscodeshiftExecutable = require.resolve('.bin/jscodeshift');
|
|
28
28
|
exports.transformerDirectory = path_1.default.join(__dirname, '../', 'transforms');
|
|
29
29
|
function checkGitStatus(force) {
|
|
@@ -87,12 +87,16 @@ function runTransform({ files, flags, transformer }) {
|
|
|
87
87
|
if (!dry && transformer === 'built-in-next-font') {
|
|
88
88
|
console.log('Uninstalling `@next/font`');
|
|
89
89
|
try {
|
|
90
|
-
(0,
|
|
90
|
+
(0, handle_package_1.uninstallPackage)('@next/font');
|
|
91
91
|
}
|
|
92
92
|
catch (_a) {
|
|
93
93
|
console.error("Couldn't uninstall `@next/font`, please uninstall it manually");
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
+
if (!dry && transformer === 'next-request-geo-ip') {
|
|
97
|
+
console.log('Installing `@vercel/functions`...');
|
|
98
|
+
(0, handle_package_1.installPackage)('@vercel/functions');
|
|
99
|
+
}
|
|
96
100
|
}
|
|
97
101
|
const TRANSFORMER_INQUIRER_CHOICES = [
|
|
98
102
|
{
|
|
@@ -143,6 +147,14 @@ const TRANSFORMER_INQUIRER_CHOICES = [
|
|
|
143
147
|
name: 'built-in-next-font: Uninstall `@next/font` and transform imports to `next/font`',
|
|
144
148
|
value: 'built-in-next-font',
|
|
145
149
|
},
|
|
150
|
+
{
|
|
151
|
+
name: 'next-async-request-api: Transforms usage of Next.js async Request APIs',
|
|
152
|
+
value: 'next-async-request-api',
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: 'next-request-geo-ip: Install `@vercel/functions` to replace `geo` and `ip` properties on `NextRequest`',
|
|
156
|
+
value: 'next-request-geo-ip',
|
|
157
|
+
},
|
|
146
158
|
];
|
|
147
159
|
function expandFilePathsIfNeeded(filesBeforeExpansion) {
|
|
148
160
|
const shouldExpandFiles = filesBeforeExpansion.some((file) => file.includes('*'));
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.uninstallPackage = uninstallPackage;
|
|
7
|
+
exports.installPackage = installPackage;
|
|
7
8
|
const fs_1 = __importDefault(require("fs"));
|
|
8
9
|
const path_1 = __importDefault(require("path"));
|
|
9
10
|
const execa_1 = __importDefault(require("execa"));
|
|
@@ -28,4 +29,15 @@ function uninstallPackage(packageToUninstall) {
|
|
|
28
29
|
}
|
|
29
30
|
execa_1.default.sync(pkgManager, [command, packageToUninstall], { stdio: 'inherit' });
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
+
function installPackage(packageToInstall) {
|
|
33
|
+
const pkgManager = getPkgManager(process.cwd());
|
|
34
|
+
if (!pkgManager)
|
|
35
|
+
throw new Error('Failed to find package manager');
|
|
36
|
+
try {
|
|
37
|
+
execa_1.default.sync(pkgManager, ['add', packageToInstall], { stdio: 'inherit' });
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
throw new Error(`Failed to install "${packageToInstall}". Please install it manually.`, { cause: error });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=handle-package.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@next/codemod",
|
|
3
|
-
"version": "15.0.0-canary.
|
|
3
|
+
"version": "15.0.0-canary.164",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"transforms/*.js",
|
|
22
|
+
"transforms/lib/**/*.js",
|
|
22
23
|
"bin/*.js",
|
|
23
24
|
"lib/**/*.js",
|
|
24
25
|
"lib/cra-to-next/gitignore"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = transform;
|
|
4
|
+
const next_async_dynamic_prop_1 = require("./next-async-dynamic-prop");
|
|
5
|
+
const next_async_dynamic_api_1 = require("./next-async-dynamic-api");
|
|
6
|
+
function transform(file, api) {
|
|
7
|
+
const filePath = file.path;
|
|
8
|
+
// if it's node_modules or types file, skip
|
|
9
|
+
if (/node_modules/.test(filePath) || /\.d\.(m|c)?ts$/.test(filePath)) {
|
|
10
|
+
return file.source;
|
|
11
|
+
}
|
|
12
|
+
const transforms = [next_async_dynamic_prop_1.transformDynamicProps, next_async_dynamic_api_1.transformDynamicAPI];
|
|
13
|
+
return transforms.reduce((source, transformFn) => {
|
|
14
|
+
const result = transformFn(source, api, file.path);
|
|
15
|
+
if (!result) {
|
|
16
|
+
return source;
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}, file.source);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.transformDynamicAPI = transformDynamicAPI;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
function wrapParathnessIfNeeded(hasChainAccess, j, expression) {
|
|
6
|
+
return hasChainAccess ? j.parenthesizedExpression(expression) : expression;
|
|
7
|
+
}
|
|
8
|
+
function transformDynamicAPI(source, api, filePath) {
|
|
9
|
+
const j = api.jscodeshift.withParser('tsx');
|
|
10
|
+
const root = j(source);
|
|
11
|
+
let modified = false;
|
|
12
|
+
// Check if 'use' from 'react' needs to be imported
|
|
13
|
+
let needsReactUseImport = false;
|
|
14
|
+
const insertedTypes = new Set();
|
|
15
|
+
function isImportedInModule(path, functionName) {
|
|
16
|
+
const closestDef = j(path)
|
|
17
|
+
.closestScope()
|
|
18
|
+
.findVariableDeclarators(functionName);
|
|
19
|
+
return closestDef.size() === 0;
|
|
20
|
+
}
|
|
21
|
+
function processAsyncApiCalls(asyncRequestApiName, originRequestApiName) {
|
|
22
|
+
// Process each call to cookies() or headers()
|
|
23
|
+
root
|
|
24
|
+
.find(j.CallExpression, {
|
|
25
|
+
callee: {
|
|
26
|
+
type: 'Identifier',
|
|
27
|
+
name: asyncRequestApiName,
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
.forEach((path) => {
|
|
31
|
+
var _a, _b, _c;
|
|
32
|
+
const isImportedTopLevel = isImportedInModule(path, asyncRequestApiName);
|
|
33
|
+
if (!isImportedTopLevel) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const closetScope = j(path).closestScope();
|
|
37
|
+
// First search the closed function of the current path
|
|
38
|
+
let closestFunction;
|
|
39
|
+
closestFunction = j(path).closest(j.FunctionDeclaration);
|
|
40
|
+
if (closestFunction.size() === 0) {
|
|
41
|
+
closestFunction = j(path).closest(j.FunctionExpression);
|
|
42
|
+
}
|
|
43
|
+
if (closestFunction.size() === 0) {
|
|
44
|
+
closestFunction = j(path).closest(j.ArrowFunctionExpression);
|
|
45
|
+
}
|
|
46
|
+
const isAsyncFunction = closestFunction
|
|
47
|
+
.nodes()
|
|
48
|
+
.some((node) => node.async);
|
|
49
|
+
const isCallAwaited = ((_b = (_a = path.parentPath) === null || _a === void 0 ? void 0 : _a.node) === null || _b === void 0 ? void 0 : _b.type) === 'AwaitExpression';
|
|
50
|
+
const hasChainAccess = path.parentPath.value.type === 'MemberExpression' &&
|
|
51
|
+
path.parentPath.value.object === path.node;
|
|
52
|
+
// For cookies/headers API, only transform server and shared components
|
|
53
|
+
if (isAsyncFunction) {
|
|
54
|
+
if (!isCallAwaited) {
|
|
55
|
+
// Add 'await' in front of cookies() call
|
|
56
|
+
const expr = j.awaitExpression(
|
|
57
|
+
// add parentheses to wrap the function call
|
|
58
|
+
j.callExpression(j.identifier(asyncRequestApiName), []));
|
|
59
|
+
j(path).replaceWith(wrapParathnessIfNeeded(hasChainAccess, j, expr));
|
|
60
|
+
modified = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Determine if the function is an export
|
|
65
|
+
const closetScopePath = closetScope.get();
|
|
66
|
+
const isFromExport = (0, utils_1.isMatchedFunctionExported)(closetScopePath, j);
|
|
67
|
+
const closestFunctionNode = closetScope.size()
|
|
68
|
+
? closetScopePath.node
|
|
69
|
+
: null;
|
|
70
|
+
// If it's exporting a function directly, exportFunctionNode is same as exportNode
|
|
71
|
+
// e.g. export default function MyComponent() {}
|
|
72
|
+
// If it's exporting a variable declaration, exportFunctionNode is the function declaration
|
|
73
|
+
// e.g. export const MyComponent = function() {}
|
|
74
|
+
let exportFunctionNode;
|
|
75
|
+
if (isFromExport) {
|
|
76
|
+
if (closestFunctionNode &&
|
|
77
|
+
(0, utils_1.isFunctionType)(closestFunctionNode.type)) {
|
|
78
|
+
exportFunctionNode = closestFunctionNode;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Is normal async function
|
|
83
|
+
exportFunctionNode = closestFunctionNode;
|
|
84
|
+
}
|
|
85
|
+
let canConvertToAsync = false;
|
|
86
|
+
// check if current path is under the default export function
|
|
87
|
+
if (isFromExport) {
|
|
88
|
+
// if default export function is not async, convert it to async, and await the api call
|
|
89
|
+
if (!isCallAwaited) {
|
|
90
|
+
// If the scoped function is async function
|
|
91
|
+
if ((0, utils_1.isFunctionType)(exportFunctionNode.type) &&
|
|
92
|
+
exportFunctionNode.async === false) {
|
|
93
|
+
canConvertToAsync = true;
|
|
94
|
+
exportFunctionNode.async = true;
|
|
95
|
+
}
|
|
96
|
+
if (canConvertToAsync) {
|
|
97
|
+
const expr = j.awaitExpression(j.callExpression(j.identifier(asyncRequestApiName), []));
|
|
98
|
+
j(path).replaceWith(wrapParathnessIfNeeded(hasChainAccess, j, expr));
|
|
99
|
+
(0, utils_1.turnFunctionReturnTypeToAsync)(closetScopePath.node, j);
|
|
100
|
+
modified = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// if parent is function and it's a hook, which starts with 'use', wrap the api call with 'use()'
|
|
106
|
+
const parentFunction = j(path).closest(j.FunctionDeclaration) ||
|
|
107
|
+
j(path).closest(j.FunctionExpression) ||
|
|
108
|
+
j(path).closest(j.ArrowFunctionExpression);
|
|
109
|
+
if (parentFunction.size() > 0) {
|
|
110
|
+
const parentFunctionName = (_c = parentFunction.get().node.id) === null || _c === void 0 ? void 0 : _c.name;
|
|
111
|
+
const isParentFunctionHook = parentFunctionName === null || parentFunctionName === void 0 ? void 0 : parentFunctionName.startsWith('use');
|
|
112
|
+
if (isParentFunctionHook) {
|
|
113
|
+
j(path).replaceWith(j.callExpression(j.identifier('use'), [
|
|
114
|
+
j.callExpression(j.identifier(asyncRequestApiName), []),
|
|
115
|
+
]));
|
|
116
|
+
needsReactUseImport = true;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes);
|
|
124
|
+
}
|
|
125
|
+
modified = true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const isClientComponent = (0, utils_1.determineClientDirective)(root, j, source);
|
|
131
|
+
const importedNextAsyncRequestApisMapping = findImportMappingFromNextHeaders(root, j);
|
|
132
|
+
// Only transform the valid calls in server or shared components
|
|
133
|
+
if (!isClientComponent) {
|
|
134
|
+
for (const originName in importedNextAsyncRequestApisMapping) {
|
|
135
|
+
const aliasName = importedNextAsyncRequestApisMapping[originName];
|
|
136
|
+
processAsyncApiCalls(aliasName, originName);
|
|
137
|
+
}
|
|
138
|
+
// Add import { use } from 'react' if needed and not already imported
|
|
139
|
+
if (needsReactUseImport) {
|
|
140
|
+
(0, utils_1.insertReactUseImport)(root, j);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return modified ? root.toSource() : null;
|
|
144
|
+
}
|
|
145
|
+
// cast to unknown first, then the specific type
|
|
146
|
+
const API_CAST_TYPE_MAP = {
|
|
147
|
+
cookies: 'UnsafeUnwrappedCookies',
|
|
148
|
+
headers: 'UnsafeUnwrappedHeaders',
|
|
149
|
+
draftMode: 'UnsafeUnwrappedDraftMode',
|
|
150
|
+
};
|
|
151
|
+
function castTypesOrAddComment(j, path, originRequestApiName, root, filePath, insertedTypes) {
|
|
152
|
+
const isTsFile = filePath.endsWith('.ts') || filePath.endsWith('.tsx');
|
|
153
|
+
if (isTsFile) {
|
|
154
|
+
/* Do type cast for headers, cookies, draftMode
|
|
155
|
+
import {
|
|
156
|
+
type UnsafeUnwrappedHeaders,
|
|
157
|
+
type UnsafeUnwrappedCookies,
|
|
158
|
+
type UnsafeUnwrappedDraftMode
|
|
159
|
+
} from 'next/headers'
|
|
160
|
+
|
|
161
|
+
cookies() as unknown as UnsafeUnwrappedCookies
|
|
162
|
+
headers() as unknown as UnsafeUnwrappedHeaders
|
|
163
|
+
draftMode() as unknown as UnsafeUnwrappedDraftMode
|
|
164
|
+
|
|
165
|
+
e.g. `<path>` is cookies(), convert it to `(<path> as unknown as UnsafeUnwrappedCookies)`
|
|
166
|
+
*/
|
|
167
|
+
const targetType = API_CAST_TYPE_MAP[originRequestApiName];
|
|
168
|
+
const newCastExpression = j.tsAsExpression(j.tsAsExpression(path.node, j.tsUnknownKeyword()), j.tsTypeReference(j.identifier(targetType)));
|
|
169
|
+
// Replace the original expression with the new cast expression,
|
|
170
|
+
// also wrap () around the new cast expression.
|
|
171
|
+
j(path).replaceWith(j.parenthesizedExpression(newCastExpression));
|
|
172
|
+
// If cast types are not imported, add them to the import list
|
|
173
|
+
const importDeclaration = root.find(j.ImportDeclaration, {
|
|
174
|
+
source: { value: 'next/headers' },
|
|
175
|
+
});
|
|
176
|
+
if (importDeclaration.size() > 0) {
|
|
177
|
+
const hasImportedType = importDeclaration
|
|
178
|
+
.find(j.TSTypeAliasDeclaration, {
|
|
179
|
+
id: { name: targetType },
|
|
180
|
+
})
|
|
181
|
+
.size() > 0 ||
|
|
182
|
+
importDeclaration
|
|
183
|
+
.find(j.ImportSpecifier, {
|
|
184
|
+
imported: { name: targetType },
|
|
185
|
+
})
|
|
186
|
+
.size() > 0;
|
|
187
|
+
if (!hasImportedType && !insertedTypes.has(targetType)) {
|
|
188
|
+
importDeclaration
|
|
189
|
+
.get()
|
|
190
|
+
.node.specifiers.push(j.importSpecifier(j.identifier(`type ${targetType}`)));
|
|
191
|
+
insertedTypes.add(targetType);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// Otherwise for JS file, leave a message to the user to manually handle the transformation
|
|
197
|
+
path.node.comments = [
|
|
198
|
+
j.commentBlock(' TODO: please manually await this call, codemod cannot transform due to undetermined async scope '),
|
|
199
|
+
...(path.node.comments || []),
|
|
200
|
+
];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function findImportMappingFromNextHeaders(root, j) {
|
|
204
|
+
const mappings = {};
|
|
205
|
+
// Find the import declaration from 'next/headers'
|
|
206
|
+
root
|
|
207
|
+
.find(j.ImportDeclaration, { source: { value: 'next/headers' } })
|
|
208
|
+
.forEach((importPath) => {
|
|
209
|
+
const importDeclaration = importPath.node;
|
|
210
|
+
// Iterate over the specifiers and build the mappings
|
|
211
|
+
importDeclaration.specifiers.forEach((specifier) => {
|
|
212
|
+
if (j.ImportSpecifier.check(specifier)) {
|
|
213
|
+
const importedName = specifier.imported.name; // Original name (e.g., cookies)
|
|
214
|
+
const localName = specifier.local.name; // Local name (e.g., myCookies or same as importedName)
|
|
215
|
+
// Add to the mappings
|
|
216
|
+
mappings[importedName] = localName;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
return mappings;
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=next-async-dynamic-api.js.map
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.transformDynamicProps = transformDynamicProps;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
const PAGE_PROPS = 'props';
|
|
6
|
+
function isAsyncFunctionDeclaration(path) {
|
|
7
|
+
const decl = path.value.declaration;
|
|
8
|
+
const isAsyncFunction = (decl.type === 'FunctionDeclaration' ||
|
|
9
|
+
decl.type === 'FunctionExpression' ||
|
|
10
|
+
decl.type === 'ArrowFunctionExpression') &&
|
|
11
|
+
decl.async;
|
|
12
|
+
return isAsyncFunction;
|
|
13
|
+
}
|
|
14
|
+
function transformDynamicProps(source, api, _filePath) {
|
|
15
|
+
let modified = false;
|
|
16
|
+
const j = api.jscodeshift.withParser('tsx');
|
|
17
|
+
const root = j(source);
|
|
18
|
+
// Check if 'use' from 'react' needs to be imported
|
|
19
|
+
let needsReactUseImport = false;
|
|
20
|
+
// Based on the prop names
|
|
21
|
+
// e.g. destruct `params` { slug } = params
|
|
22
|
+
// e.g. destruct `searchParams `{ search } = searchParams
|
|
23
|
+
let insertedDestructPropNames = new Set();
|
|
24
|
+
// Rename props to `prop` argument for the function
|
|
25
|
+
let insertedRenamedPropFunctionNames = new Set();
|
|
26
|
+
function processAsyncPropOfEntryFile(isClientComponent) {
|
|
27
|
+
// find `params` and `searchParams` in file, and transform the access to them
|
|
28
|
+
function renameAsyncPropIfExisted(path) {
|
|
29
|
+
var _a;
|
|
30
|
+
const decl = path.value.declaration;
|
|
31
|
+
if (decl.type !== 'FunctionDeclaration' &&
|
|
32
|
+
decl.type !== 'FunctionExpression' &&
|
|
33
|
+
decl.type !== 'ArrowFunctionExpression') {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const params = decl.params;
|
|
37
|
+
const propertiesMap = new Map();
|
|
38
|
+
// If there's no first param, return
|
|
39
|
+
if (params.length !== 1) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const propsIdentifier = (0, utils_1.generateUniqueIdentifier)(PAGE_PROPS, path, j);
|
|
43
|
+
const currentParam = params[0];
|
|
44
|
+
// Argument destructuring case
|
|
45
|
+
if (currentParam.type === 'ObjectPattern') {
|
|
46
|
+
// Validate if the properties are not `params` and `searchParams`,
|
|
47
|
+
// if they are, quit the transformation
|
|
48
|
+
for (const prop of currentParam.properties) {
|
|
49
|
+
if ('key' in prop && prop.key.type === 'Identifier') {
|
|
50
|
+
const propName = prop.key.name;
|
|
51
|
+
if (!utils_1.TARGET_PROP_NAMES.has(propName)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
currentParam.properties.forEach((prop) => {
|
|
57
|
+
if (
|
|
58
|
+
// Could be `Property` or `ObjectProperty`
|
|
59
|
+
'key' in prop &&
|
|
60
|
+
prop.key.type === 'Identifier') {
|
|
61
|
+
const value = 'value' in prop ? prop.value : null;
|
|
62
|
+
propertiesMap.set(prop.key.name, value);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
const paramTypeAnnotation = currentParam.typeAnnotation;
|
|
66
|
+
if (paramTypeAnnotation && paramTypeAnnotation.typeAnnotation) {
|
|
67
|
+
const typeAnnotation = paramTypeAnnotation.typeAnnotation;
|
|
68
|
+
if (typeAnnotation.type === 'TSTypeLiteral') {
|
|
69
|
+
const typeLiteral = typeAnnotation;
|
|
70
|
+
// Find the type property for `params`
|
|
71
|
+
typeLiteral.members.forEach((member) => {
|
|
72
|
+
if (member.type === 'TSPropertySignature' &&
|
|
73
|
+
member.key.type === 'Identifier' &&
|
|
74
|
+
propertiesMap.has(member.key.name)) {
|
|
75
|
+
// if it's already a Promise, don't wrap it again, return
|
|
76
|
+
if (member.typeAnnotation &&
|
|
77
|
+
member.typeAnnotation.typeAnnotation &&
|
|
78
|
+
member.typeAnnotation.typeAnnotation.type ===
|
|
79
|
+
'TSTypeReference' &&
|
|
80
|
+
member.typeAnnotation.typeAnnotation.typeName.type ===
|
|
81
|
+
'Identifier' &&
|
|
82
|
+
member.typeAnnotation.typeAnnotation.typeName.name ===
|
|
83
|
+
'Promise') {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Wrap the `params` type in Promise<>
|
|
87
|
+
if (member.typeAnnotation &&
|
|
88
|
+
member.typeAnnotation.typeAnnotation &&
|
|
89
|
+
j.TSType.check(member.typeAnnotation.typeAnnotation)) {
|
|
90
|
+
member.typeAnnotation.typeAnnotation = j.tsTypeReference(j.identifier('Promise'), j.tsTypeParameterInstantiation([
|
|
91
|
+
// @ts-ignore
|
|
92
|
+
member.typeAnnotation.typeAnnotation,
|
|
93
|
+
]));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
else if (typeAnnotation.type === 'TSTypeReference') {
|
|
99
|
+
// If typeAnnotation is a type or interface, change the properties to Promise<type of property>
|
|
100
|
+
// e.g. interface PageProps { params: { slug: string } } => interface PageProps { params: Promise<{ slug: string }> }
|
|
101
|
+
const typeReference = typeAnnotation;
|
|
102
|
+
if (typeReference.typeName.type === 'Identifier') {
|
|
103
|
+
// Find the actual type of the type reference
|
|
104
|
+
const foundTypes = findAllTypes(root, j, typeReference.typeName.name);
|
|
105
|
+
// Deal with interfaces
|
|
106
|
+
if (foundTypes.interfaces.length > 0) {
|
|
107
|
+
const interfaceDeclaration = foundTypes.interfaces[0];
|
|
108
|
+
if (interfaceDeclaration.type === 'TSInterfaceDeclaration' &&
|
|
109
|
+
((_a = interfaceDeclaration.body) === null || _a === void 0 ? void 0 : _a.type) === 'TSInterfaceBody') {
|
|
110
|
+
const typeBody = interfaceDeclaration.body.body;
|
|
111
|
+
// if it's already a Promise, don't wrap it again, return
|
|
112
|
+
// traverse the typeReference's properties, if any is in propNames, wrap it in Promise<> if needed
|
|
113
|
+
typeBody.forEach((member) => {
|
|
114
|
+
var _a, _b, _c;
|
|
115
|
+
if (member.type === 'TSPropertySignature' &&
|
|
116
|
+
member.key.type === 'Identifier' &&
|
|
117
|
+
utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
|
|
118
|
+
// if it's already a Promise, don't wrap it again, return
|
|
119
|
+
if (member.typeAnnotation &&
|
|
120
|
+
member.typeAnnotation.typeAnnotation &&
|
|
121
|
+
((_c = (_b = (_a = member.typeAnnotation) === null || _a === void 0 ? void 0 : _a.typeAnnotation) === null || _b === void 0 ? void 0 : _b.typeName) === null || _c === void 0 ? void 0 : _c.name) === 'Promise') {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Wrap the prop type in Promise<>
|
|
125
|
+
if (member.typeAnnotation &&
|
|
126
|
+
member.typeAnnotation.typeAnnotation &&
|
|
127
|
+
// check if member name is in propNames
|
|
128
|
+
utils_1.TARGET_PROP_NAMES.has(member.key.name)) {
|
|
129
|
+
member.typeAnnotation.typeAnnotation =
|
|
130
|
+
j.tsTypeReference(j.identifier('Promise'), j.tsTypeParameterInstantiation([
|
|
131
|
+
member.typeAnnotation.typeAnnotation,
|
|
132
|
+
]));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
propsIdentifier.typeAnnotation = paramTypeAnnotation;
|
|
141
|
+
}
|
|
142
|
+
// Override the first param to `props`
|
|
143
|
+
params[0] = propsIdentifier;
|
|
144
|
+
modified = true;
|
|
145
|
+
}
|
|
146
|
+
if (modified) {
|
|
147
|
+
resolveAsyncProp(path, propertiesMap, propsIdentifier.name);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function getBodyOfFunctionDeclaration(path) {
|
|
151
|
+
const decl = path.value.declaration;
|
|
152
|
+
let functionBody;
|
|
153
|
+
if (decl.type === 'FunctionDeclaration' ||
|
|
154
|
+
decl.type === 'FunctionExpression' ||
|
|
155
|
+
decl.type === 'ArrowFunctionExpression') {
|
|
156
|
+
if (decl.body && decl.body.type === 'BlockStatement') {
|
|
157
|
+
functionBody = decl.body.body;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return functionBody;
|
|
161
|
+
}
|
|
162
|
+
// Helper function to insert `const params = await asyncParams;` at the beginning of the function body
|
|
163
|
+
function resolveAsyncProp(path, propertiesMap, propsIdentifierName) {
|
|
164
|
+
var _a;
|
|
165
|
+
const isDefaultExport = path.value.type === 'ExportDefaultDeclaration';
|
|
166
|
+
// If it's sync default export, and it's also server component, make the function async
|
|
167
|
+
if (isDefaultExport &&
|
|
168
|
+
!isClientComponent &&
|
|
169
|
+
!isAsyncFunctionDeclaration(path)) {
|
|
170
|
+
if ('async' in path.value.declaration) {
|
|
171
|
+
path.value.declaration.async = true;
|
|
172
|
+
(0, utils_1.turnFunctionReturnTypeToAsync)(path.value.declaration, j);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const isAsyncFunc = isAsyncFunctionDeclaration(path);
|
|
176
|
+
// @ts-ignore quick way to check if it's a function and it has a name
|
|
177
|
+
const functionName = ((_a = path.value.declaration.id) === null || _a === void 0 ? void 0 : _a.name) || 'default';
|
|
178
|
+
const functionBody = getBodyOfFunctionDeclaration(path);
|
|
179
|
+
for (const [propName, paramsProperty] of propertiesMap) {
|
|
180
|
+
const propNameIdentifier = j.identifier(propName);
|
|
181
|
+
const propsIdentifier = j.identifier(propsIdentifierName);
|
|
182
|
+
const accessedPropId = j.memberExpression(propsIdentifier, propNameIdentifier);
|
|
183
|
+
// Check param property value, if it's destructed, we need to destruct it as well
|
|
184
|
+
// e.g.
|
|
185
|
+
// input: Page({ params: { slug } })
|
|
186
|
+
// output: const { slug } = await props.params; rather than const props = await props.params;
|
|
187
|
+
const uid = functionName + ':' + propName;
|
|
188
|
+
if ((paramsProperty === null || paramsProperty === void 0 ? void 0 : paramsProperty.type) === 'ObjectPattern') {
|
|
189
|
+
const objectPattern = paramsProperty;
|
|
190
|
+
const objectPatternProperties = objectPattern.properties;
|
|
191
|
+
// destruct the object pattern, e.g. { slug } => const { slug } = params;
|
|
192
|
+
const destructedObjectPattern = j.variableDeclaration('const', [
|
|
193
|
+
j.variableDeclarator(j.objectPattern(objectPatternProperties.map((prop) => {
|
|
194
|
+
if (prop.type === 'Property' &&
|
|
195
|
+
prop.key.type === 'Identifier') {
|
|
196
|
+
return j.objectProperty(j.identifier(prop.key.name), j.identifier(prop.key.name));
|
|
197
|
+
}
|
|
198
|
+
return prop;
|
|
199
|
+
})), propNameIdentifier),
|
|
200
|
+
]);
|
|
201
|
+
if (!insertedDestructPropNames.has(uid) && functionBody) {
|
|
202
|
+
functionBody.unshift(destructedObjectPattern);
|
|
203
|
+
insertedDestructPropNames.add(uid);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (isAsyncFunc) {
|
|
207
|
+
// If it's async function, add await to the async props.<propName>
|
|
208
|
+
const paramAssignment = j.variableDeclaration('const', [
|
|
209
|
+
j.variableDeclarator(j.identifier(propName), j.awaitExpression(accessedPropId)),
|
|
210
|
+
]);
|
|
211
|
+
if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
|
|
212
|
+
functionBody.unshift(paramAssignment);
|
|
213
|
+
insertedRenamedPropFunctionNames.add(uid);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
const isFromExport = path.value.type === 'ExportNamedDeclaration';
|
|
218
|
+
if (isFromExport) {
|
|
219
|
+
// If it's export function, populate the function to async
|
|
220
|
+
if ((0, utils_1.isFunctionType)(path.value.declaration.type) &&
|
|
221
|
+
// Make TS happy
|
|
222
|
+
'async' in path.value.declaration) {
|
|
223
|
+
path.value.declaration.async = true;
|
|
224
|
+
(0, utils_1.turnFunctionReturnTypeToAsync)(path.value.declaration, j);
|
|
225
|
+
// Insert `const <propName> = await props.<propName>;` at the beginning of the function body
|
|
226
|
+
const paramAssignment = j.variableDeclaration('const', [
|
|
227
|
+
j.variableDeclarator(j.identifier(propName), j.awaitExpression(accessedPropId)),
|
|
228
|
+
]);
|
|
229
|
+
if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
|
|
230
|
+
functionBody.unshift(paramAssignment);
|
|
231
|
+
insertedRenamedPropFunctionNames.add(uid);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
const paramAssignment = j.variableDeclaration('const', [
|
|
237
|
+
j.variableDeclarator(j.identifier(propName), j.callExpression(j.identifier('use'), [accessedPropId])),
|
|
238
|
+
]);
|
|
239
|
+
if (!insertedRenamedPropFunctionNames.has(uid) && functionBody) {
|
|
240
|
+
needsReactUseImport = true;
|
|
241
|
+
functionBody.unshift(paramAssignment);
|
|
242
|
+
insertedRenamedPropFunctionNames.add(uid);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Process Function Declarations
|
|
249
|
+
// Matching: default export function XXX(...) { ... }
|
|
250
|
+
const defaultExportFunctionDeclarations = root.find(j.ExportDefaultDeclaration, {
|
|
251
|
+
declaration: {
|
|
252
|
+
type: (type) => type === 'FunctionDeclaration' ||
|
|
253
|
+
type === 'FunctionExpression' ||
|
|
254
|
+
type === 'ArrowFunctionExpression',
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
defaultExportFunctionDeclarations.forEach((path) => {
|
|
258
|
+
renameAsyncPropIfExisted(path);
|
|
259
|
+
});
|
|
260
|
+
// Matching Next.js functional named export of route entry:
|
|
261
|
+
// - export function <named>(...) { ... }
|
|
262
|
+
// - export const <named> = ...
|
|
263
|
+
const targetNamedExportDeclarations = root.find(j.ExportNamedDeclaration,
|
|
264
|
+
// Filter the name is in TARGET_NAMED_EXPORTS
|
|
265
|
+
{
|
|
266
|
+
declaration: {
|
|
267
|
+
id: {
|
|
268
|
+
name: (idName) => {
|
|
269
|
+
return utils_1.TARGET_NAMED_EXPORTS.has(idName);
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
targetNamedExportDeclarations.forEach((path) => {
|
|
275
|
+
renameAsyncPropIfExisted(path);
|
|
276
|
+
});
|
|
277
|
+
// TDOO: handle targetNamedDeclarators
|
|
278
|
+
// const targetNamedDeclarators = root.find(
|
|
279
|
+
// j.VariableDeclarator,
|
|
280
|
+
// (node) =>
|
|
281
|
+
// node.id.type === 'Identifier' &&
|
|
282
|
+
// TARGET_NAMED_EXPORTS.has(node.id.name)
|
|
283
|
+
// )
|
|
284
|
+
}
|
|
285
|
+
const isClientComponent = (0, utils_1.determineClientDirective)(root, j, source);
|
|
286
|
+
// Apply to `params` and `searchParams`
|
|
287
|
+
processAsyncPropOfEntryFile(isClientComponent);
|
|
288
|
+
// Add import { use } from 'react' if needed and not already imported
|
|
289
|
+
if (needsReactUseImport) {
|
|
290
|
+
(0, utils_1.insertReactUseImport)(root, j);
|
|
291
|
+
}
|
|
292
|
+
return modified ? root.toSource() : null;
|
|
293
|
+
}
|
|
294
|
+
function findAllTypes(root, j, typeName) {
|
|
295
|
+
const types = {
|
|
296
|
+
interfaces: [],
|
|
297
|
+
typeAliases: [],
|
|
298
|
+
imports: [],
|
|
299
|
+
references: [],
|
|
300
|
+
};
|
|
301
|
+
// Step 1: Find all interface declarations with the specified name
|
|
302
|
+
root
|
|
303
|
+
.find(j.TSInterfaceDeclaration, {
|
|
304
|
+
id: {
|
|
305
|
+
type: 'Identifier',
|
|
306
|
+
name: typeName,
|
|
307
|
+
},
|
|
308
|
+
})
|
|
309
|
+
.forEach((path) => {
|
|
310
|
+
types.interfaces.push(path.node);
|
|
311
|
+
});
|
|
312
|
+
// Step 2: Find all type alias declarations with the specified name
|
|
313
|
+
root
|
|
314
|
+
.find(j.TSTypeAliasDeclaration, {
|
|
315
|
+
id: {
|
|
316
|
+
type: 'Identifier',
|
|
317
|
+
name: typeName,
|
|
318
|
+
},
|
|
319
|
+
})
|
|
320
|
+
.forEach((path) => {
|
|
321
|
+
types.typeAliases.push(path.node);
|
|
322
|
+
});
|
|
323
|
+
// Step 3: Find all imported types with the specified name
|
|
324
|
+
root
|
|
325
|
+
.find(j.ImportSpecifier, {
|
|
326
|
+
imported: {
|
|
327
|
+
type: 'Identifier',
|
|
328
|
+
name: typeName,
|
|
329
|
+
},
|
|
330
|
+
})
|
|
331
|
+
.forEach((path) => {
|
|
332
|
+
types.imports.push(path.node);
|
|
333
|
+
});
|
|
334
|
+
// Step 4: Find all references to the specified type
|
|
335
|
+
root
|
|
336
|
+
.find(j.TSTypeReference, {
|
|
337
|
+
typeName: {
|
|
338
|
+
type: 'Identifier',
|
|
339
|
+
name: typeName,
|
|
340
|
+
},
|
|
341
|
+
})
|
|
342
|
+
.forEach((path) => {
|
|
343
|
+
types.references.push(path.node);
|
|
344
|
+
});
|
|
345
|
+
return types;
|
|
346
|
+
}
|
|
347
|
+
//# sourceMappingURL=next-async-dynamic-prop.js.map
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TARGET_PROP_NAMES = exports.TARGET_NAMED_EXPORTS = void 0;
|
|
4
|
+
exports.isFunctionType = isFunctionType;
|
|
5
|
+
exports.isMatchedFunctionExported = isMatchedFunctionExported;
|
|
6
|
+
exports.determineClientDirective = determineClientDirective;
|
|
7
|
+
exports.isPromiseType = isPromiseType;
|
|
8
|
+
exports.turnFunctionReturnTypeToAsync = turnFunctionReturnTypeToAsync;
|
|
9
|
+
exports.insertReactUseImport = insertReactUseImport;
|
|
10
|
+
exports.generateUniqueIdentifier = generateUniqueIdentifier;
|
|
11
|
+
exports.TARGET_NAMED_EXPORTS = new Set([
|
|
12
|
+
// For custom route
|
|
13
|
+
'GET',
|
|
14
|
+
'HEAD',
|
|
15
|
+
'POST',
|
|
16
|
+
'PUT',
|
|
17
|
+
'DELETE',
|
|
18
|
+
'PATCH',
|
|
19
|
+
'OPTIONS',
|
|
20
|
+
// For page and layout
|
|
21
|
+
'generateMetadata',
|
|
22
|
+
]);
|
|
23
|
+
exports.TARGET_PROP_NAMES = new Set(['params', 'searchParams']);
|
|
24
|
+
function isFunctionType(type) {
|
|
25
|
+
return (type === 'FunctionDeclaration' ||
|
|
26
|
+
type === 'FunctionExpression' ||
|
|
27
|
+
type === 'ArrowFunctionExpression');
|
|
28
|
+
}
|
|
29
|
+
function isMatchedFunctionExported(path, j) {
|
|
30
|
+
const matchedFunctionNameFilter = (idName) => exports.TARGET_NAMED_EXPORTS.has(idName);
|
|
31
|
+
const directNamedExport = j(path).closest(j.ExportNamedDeclaration, {
|
|
32
|
+
declaration: {
|
|
33
|
+
type: 'FunctionDeclaration',
|
|
34
|
+
id: {
|
|
35
|
+
name: matchedFunctionNameFilter,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
if (directNamedExport.size() > 0) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
// Check for default export (`export default function() {}`)
|
|
43
|
+
const isDefaultExport = j(path).closest(j.ExportDefaultDeclaration).size() > 0;
|
|
44
|
+
if (isDefaultExport) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
// Look for named export elsewhere in the file (`export { <named> }`)
|
|
48
|
+
const root = j(path).closestScope().closest(j.Program);
|
|
49
|
+
const isNamedExport = root
|
|
50
|
+
.find(j.ExportNamedDeclaration, {
|
|
51
|
+
specifiers: [
|
|
52
|
+
{
|
|
53
|
+
type: 'ExportSpecifier',
|
|
54
|
+
exported: {
|
|
55
|
+
name: matchedFunctionNameFilter,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
})
|
|
60
|
+
.size() > 0;
|
|
61
|
+
// Look for variable export but still function, e.g. `export const <named> = function() {}`,
|
|
62
|
+
// also check if variable is a function or arrow function
|
|
63
|
+
const isVariableExport = root
|
|
64
|
+
.find(j.ExportNamedDeclaration, {
|
|
65
|
+
declaration: {
|
|
66
|
+
declarations: [
|
|
67
|
+
{
|
|
68
|
+
type: 'VariableDeclarator',
|
|
69
|
+
id: {
|
|
70
|
+
type: 'Identifier',
|
|
71
|
+
name: matchedFunctionNameFilter,
|
|
72
|
+
},
|
|
73
|
+
init: {
|
|
74
|
+
type: isFunctionType,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
.size() > 0;
|
|
81
|
+
if (isVariableExport)
|
|
82
|
+
return true;
|
|
83
|
+
return isNamedExport;
|
|
84
|
+
}
|
|
85
|
+
function determineClientDirective(root, j, source) {
|
|
86
|
+
const hasStringDirective = root
|
|
87
|
+
.find(j.Literal)
|
|
88
|
+
.filter((path) => {
|
|
89
|
+
const expr = path.node;
|
|
90
|
+
return (expr.value === 'use client' && path.parentPath.node.type === 'Program');
|
|
91
|
+
})
|
|
92
|
+
.size() > 0;
|
|
93
|
+
// 'use client';
|
|
94
|
+
const hasStringDirectiveWithSemicolon = root
|
|
95
|
+
.find(j.StringLiteral)
|
|
96
|
+
.filter((path) => {
|
|
97
|
+
const expr = path.node;
|
|
98
|
+
return (expr.type === 'StringLiteral' &&
|
|
99
|
+
expr.value === 'use client' &&
|
|
100
|
+
path.parentPath.node.type === 'Program');
|
|
101
|
+
})
|
|
102
|
+
.size() > 0;
|
|
103
|
+
if (hasStringDirective || hasStringDirectiveWithSemicolon)
|
|
104
|
+
return true;
|
|
105
|
+
// Since the client detection is not reliable with AST in jscodeshift,
|
|
106
|
+
// determine if 'use client' or "use client" is leading in the source code.
|
|
107
|
+
const trimmedSource = source.trim();
|
|
108
|
+
const containsClientDirective = /^'use client'/.test(trimmedSource) || /^"use client"/g.test(trimmedSource);
|
|
109
|
+
return containsClientDirective;
|
|
110
|
+
}
|
|
111
|
+
function isPromiseType(typeAnnotation) {
|
|
112
|
+
return (typeAnnotation.type === 'TSTypeReference' &&
|
|
113
|
+
typeAnnotation.typeName.name === 'Promise');
|
|
114
|
+
}
|
|
115
|
+
function turnFunctionReturnTypeToAsync(node, j) {
|
|
116
|
+
if (j.FunctionDeclaration.check(node) ||
|
|
117
|
+
j.FunctionExpression.check(node) ||
|
|
118
|
+
j.ArrowFunctionExpression.check(node)) {
|
|
119
|
+
if (node.returnType) {
|
|
120
|
+
const returnTypeAnnotation = node.returnType.typeAnnotation;
|
|
121
|
+
const isReturnTypePromise = isPromiseType(returnTypeAnnotation);
|
|
122
|
+
// Turn <return type> to Promise<return type>
|
|
123
|
+
// e.g. () => { slug: string } to () => Promise<{ slug: string }>
|
|
124
|
+
// e.g. Anything to Promise<Anything>
|
|
125
|
+
if (!isReturnTypePromise) {
|
|
126
|
+
if (node.returnType &&
|
|
127
|
+
j.TSTypeAnnotation.check(node.returnType) &&
|
|
128
|
+
(j.TSTypeReference.check(node.returnType.typeAnnotation) ||
|
|
129
|
+
j.TSUnionType.check(node.returnType.typeAnnotation) ||
|
|
130
|
+
j.TSTypePredicate.check(node.returnType.typeAnnotation))) {
|
|
131
|
+
// Change the return type to Promise<void>
|
|
132
|
+
node.returnType.typeAnnotation = j.tsTypeReference(j.identifier('Promise'),
|
|
133
|
+
// @ts-ignore ignore the super strict type checking on the type annotation
|
|
134
|
+
j.tsTypeParameterInstantiation([returnTypeAnnotation]));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function insertReactUseImport(root, j) {
|
|
141
|
+
const hasReactUseImport = root
|
|
142
|
+
.find(j.ImportSpecifier, {
|
|
143
|
+
imported: {
|
|
144
|
+
type: 'Identifier',
|
|
145
|
+
name: 'use',
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
.size() > 0;
|
|
149
|
+
if (!hasReactUseImport) {
|
|
150
|
+
const reactImportDeclaration = root.find(j.ImportDeclaration, {
|
|
151
|
+
source: {
|
|
152
|
+
type: 'Literal',
|
|
153
|
+
value: 'react',
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
if (reactImportDeclaration.size() > 0) {
|
|
157
|
+
// Add 'use' to existing 'react' import declaration
|
|
158
|
+
reactImportDeclaration
|
|
159
|
+
.get()
|
|
160
|
+
.node.specifiers.push(j.importSpecifier(j.identifier('use')));
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
// Final all type imports to 'react'
|
|
164
|
+
const reactImport = root.find(j.ImportDeclaration, {
|
|
165
|
+
source: {
|
|
166
|
+
type: 'Literal',
|
|
167
|
+
value: 'react',
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
if (reactImport.size() > 0) {
|
|
171
|
+
reactImport
|
|
172
|
+
.get()
|
|
173
|
+
.node.specifiers.push(j.importSpecifier(j.identifier('use')));
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
// Add new import declaration for 'react' and 'use'
|
|
177
|
+
root
|
|
178
|
+
.get()
|
|
179
|
+
.node.program.body.unshift(j.importDeclaration([j.importSpecifier(j.identifier('use'))], j.literal('react')));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function findSubScopeArgumentIdentifier(path, j, argName) {
|
|
185
|
+
const defCount = j(path).find(j.Identifier, { name: argName }).size();
|
|
186
|
+
return defCount > 0;
|
|
187
|
+
}
|
|
188
|
+
function generateUniqueIdentifier(defaultIdName, path, j) {
|
|
189
|
+
let idName = defaultIdName;
|
|
190
|
+
let idNameSuffix = 0;
|
|
191
|
+
while (findSubScopeArgumentIdentifier(path, j, idName)) {
|
|
192
|
+
idName = defaultIdName + idNameSuffix;
|
|
193
|
+
idNameSuffix++;
|
|
194
|
+
}
|
|
195
|
+
const propsIdentifier = j.identifier(idName);
|
|
196
|
+
return propsIdentifier;
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
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.default = void 0;
|
|
7
|
+
var index_1 = require("./lib/async-request-api/index");
|
|
8
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(index_1).default; } });
|
|
9
|
+
//# sourceMappingURL=next-async-request-api.js.map
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = default_1;
|
|
4
|
+
const GEO = 'geo';
|
|
5
|
+
const IP = 'ip';
|
|
6
|
+
const GEOLOCATION = 'geolocation';
|
|
7
|
+
const IP_ADDRESS = 'ipAddress';
|
|
8
|
+
const GEO_TYPE = 'Geo';
|
|
9
|
+
function default_1(fileInfo, api) {
|
|
10
|
+
const j = api.jscodeshift.withParser('tsx');
|
|
11
|
+
const root = j(fileInfo.source);
|
|
12
|
+
if (!root.length) {
|
|
13
|
+
return fileInfo.source;
|
|
14
|
+
}
|
|
15
|
+
const nextReqType = root
|
|
16
|
+
.find(j.FunctionDeclaration)
|
|
17
|
+
.find(j.Identifier, (id) => {
|
|
18
|
+
var _a;
|
|
19
|
+
if (((_a = id.typeAnnotation) === null || _a === void 0 ? void 0 : _a.type) !== 'TSTypeAnnotation') {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const typeAnn = id.typeAnnotation.typeAnnotation;
|
|
23
|
+
return (typeAnn.type === 'TSTypeReference' &&
|
|
24
|
+
typeAnn.typeName.type === 'Identifier' &&
|
|
25
|
+
typeAnn.typeName.name === 'NextRequest');
|
|
26
|
+
});
|
|
27
|
+
const vercelFuncImports = root.find(j.ImportDeclaration, {
|
|
28
|
+
source: {
|
|
29
|
+
value: '@vercel/functions',
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
const vercelFuncImportSpecifiers = vercelFuncImports
|
|
33
|
+
.find(j.ImportSpecifier)
|
|
34
|
+
.nodes();
|
|
35
|
+
const vercelFuncImportNames = new Set(vercelFuncImportSpecifiers.map((node) => node.imported.name));
|
|
36
|
+
const hasGeolocation = vercelFuncImportNames.has(GEOLOCATION);
|
|
37
|
+
const hasIpAddress = vercelFuncImportNames.has(IP_ADDRESS);
|
|
38
|
+
const hasGeoType = vercelFuncImportNames.has(GEO_TYPE);
|
|
39
|
+
let identifierNames = new Set();
|
|
40
|
+
// if all identifiers are already imported, we don't need to create
|
|
41
|
+
// a unique identifier for them, therefore we don't look for all
|
|
42
|
+
// identifier names in the file
|
|
43
|
+
if (!hasGeolocation || !hasIpAddress || !hasGeoType) {
|
|
44
|
+
const allIdentifiers = root.find(j.Identifier).nodes();
|
|
45
|
+
identifierNames = new Set(allIdentifiers.map((node) => node.name));
|
|
46
|
+
}
|
|
47
|
+
let geoIdentifier = hasGeolocation
|
|
48
|
+
? getExistingIdentifier(vercelFuncImportSpecifiers, GEOLOCATION)
|
|
49
|
+
: getUniqueIdentifier(identifierNames, GEOLOCATION);
|
|
50
|
+
let ipIdentifier = hasIpAddress
|
|
51
|
+
? getExistingIdentifier(vercelFuncImportSpecifiers, IP_ADDRESS)
|
|
52
|
+
: getUniqueIdentifier(identifierNames, IP_ADDRESS);
|
|
53
|
+
let geoTypeIdentifier = hasGeoType
|
|
54
|
+
? getExistingIdentifier(vercelFuncImportSpecifiers, GEO_TYPE)
|
|
55
|
+
: getUniqueIdentifier(identifierNames, GEO_TYPE);
|
|
56
|
+
let { needImportGeolocation, needImportIpAddress } = replaceGeoIpValues(j, nextReqType, geoIdentifier, ipIdentifier);
|
|
57
|
+
let { needImportGeoType, hasChangedIpType } = replaceGeoIpTypes(j, root, geoTypeIdentifier);
|
|
58
|
+
let needChanges = needImportGeolocation ||
|
|
59
|
+
needImportIpAddress ||
|
|
60
|
+
needImportGeoType ||
|
|
61
|
+
hasChangedIpType;
|
|
62
|
+
if (!needChanges) {
|
|
63
|
+
return fileInfo.source;
|
|
64
|
+
}
|
|
65
|
+
// Even if there was a change above, if there's an existing import,
|
|
66
|
+
// we don't need to import them again.
|
|
67
|
+
needImportGeolocation = !hasGeolocation && needImportGeolocation;
|
|
68
|
+
needImportIpAddress = !hasIpAddress && needImportIpAddress;
|
|
69
|
+
needImportGeoType = !hasGeoType && needImportGeoType;
|
|
70
|
+
insertImportDeclarations(j, root, vercelFuncImports, needImportGeolocation, needImportIpAddress, needImportGeoType, geoIdentifier, ipIdentifier, geoTypeIdentifier);
|
|
71
|
+
return root.toSource();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Returns an existing identifier from the Vercel functions import declaration.
|
|
75
|
+
*/
|
|
76
|
+
function getExistingIdentifier(vercelFuncImportSpecifiers, identifier) {
|
|
77
|
+
var _a;
|
|
78
|
+
const existingIdentifier = vercelFuncImportSpecifiers.find((node) => node.imported.name === identifier);
|
|
79
|
+
return (((_a = existingIdentifier === null || existingIdentifier === void 0 ? void 0 : existingIdentifier.local) === null || _a === void 0 ? void 0 : _a.name) ||
|
|
80
|
+
existingIdentifier.imported.name ||
|
|
81
|
+
identifier);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Returns a unique identifier by adding a suffix to the original identifier
|
|
85
|
+
* if it already exists in the set.
|
|
86
|
+
*/
|
|
87
|
+
function getUniqueIdentifier(identifierNames, identifier) {
|
|
88
|
+
let suffix = 1;
|
|
89
|
+
let uniqueIdentifier = identifier;
|
|
90
|
+
while (identifierNames.has(uniqueIdentifier)) {
|
|
91
|
+
uniqueIdentifier = `${identifier}${suffix}`;
|
|
92
|
+
suffix++;
|
|
93
|
+
}
|
|
94
|
+
return uniqueIdentifier;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Replaces accessors `geo` and `ip` of NextRequest with corresponding
|
|
98
|
+
* function calls from `@vercel/functions`.
|
|
99
|
+
*
|
|
100
|
+
* Creates new variable declarations for destructuring assignments.
|
|
101
|
+
*/
|
|
102
|
+
function replaceGeoIpValues(j, nextReqType, geoIdentifier, ipIdentifier) {
|
|
103
|
+
let needImportGeolocation = false;
|
|
104
|
+
let needImportIpAddress = false;
|
|
105
|
+
for (const nextReqPath of nextReqType.paths()) {
|
|
106
|
+
const fnPath = nextReqPath.parentPath.parentPath;
|
|
107
|
+
const fn = j(fnPath);
|
|
108
|
+
const blockStatement = fn.find(j.BlockStatement);
|
|
109
|
+
const varDeclarators = fn.find(j.VariableDeclarator);
|
|
110
|
+
// req.geo, req.ip
|
|
111
|
+
const geoAccesses = blockStatement.find(j.MemberExpression, (me) => me.object.type === 'Identifier' &&
|
|
112
|
+
me.object.name === nextReqPath.node.name &&
|
|
113
|
+
me.property.type === 'Identifier' &&
|
|
114
|
+
me.property.name === GEO);
|
|
115
|
+
const ipAccesses = blockStatement.find(j.MemberExpression, (me) => me.object.type === 'Identifier' &&
|
|
116
|
+
me.object.name === nextReqPath.node.name &&
|
|
117
|
+
me.property.type === 'Identifier' &&
|
|
118
|
+
me.property.name === IP);
|
|
119
|
+
// var { geo, ip } = req
|
|
120
|
+
const geoDestructuring = varDeclarators.filter((path) => path.node.id.type === 'ObjectPattern' &&
|
|
121
|
+
path.node.id.properties.some((prop) => prop.type === 'ObjectProperty' &&
|
|
122
|
+
prop.key.type === 'Identifier' &&
|
|
123
|
+
prop.key.name === GEO));
|
|
124
|
+
const ipDestructuring = varDeclarators.filter((path) => path.node.id.type === 'ObjectPattern' &&
|
|
125
|
+
path.node.id.properties.some((prop) => prop.type === 'ObjectProperty' &&
|
|
126
|
+
prop.key.type === 'Identifier' &&
|
|
127
|
+
prop.key.name === IP));
|
|
128
|
+
// geolocation(req), ipAddress(req)
|
|
129
|
+
const geoCall = j.callExpression(j.identifier(geoIdentifier), [
|
|
130
|
+
Object.assign(Object.assign({}, nextReqPath.node), { typeAnnotation: null }),
|
|
131
|
+
]);
|
|
132
|
+
const ipCall = j.callExpression(j.identifier(ipIdentifier), [
|
|
133
|
+
Object.assign(Object.assign({}, nextReqPath.node), { typeAnnotation: null }),
|
|
134
|
+
]);
|
|
135
|
+
geoAccesses.replaceWith(geoCall);
|
|
136
|
+
ipAccesses.replaceWith(ipCall);
|
|
137
|
+
/**
|
|
138
|
+
* For each destructuring assignment, we create a new variable
|
|
139
|
+
* declaration and insert it after the current block statement.
|
|
140
|
+
*
|
|
141
|
+
* Before:
|
|
142
|
+
*
|
|
143
|
+
* ```
|
|
144
|
+
* const { buildId, geo, ip } = req;
|
|
145
|
+
* ```
|
|
146
|
+
*
|
|
147
|
+
* After:
|
|
148
|
+
*
|
|
149
|
+
* ```
|
|
150
|
+
* const { buildId } = req;
|
|
151
|
+
* const geo = geolocation(req);
|
|
152
|
+
* const ip = ipAddress(req);
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
geoDestructuring.forEach((path) => {
|
|
156
|
+
if (path.node.id.type === 'ObjectPattern') {
|
|
157
|
+
const properties = path.node.id.properties;
|
|
158
|
+
const geoProperty = properties.find((prop) => prop.type === 'ObjectProperty' &&
|
|
159
|
+
prop.key.type === 'Identifier' &&
|
|
160
|
+
prop.key.name === GEO);
|
|
161
|
+
const otherProperties = properties.filter((prop) => prop !== geoProperty);
|
|
162
|
+
const geoDeclaration = j.variableDeclaration('const', [
|
|
163
|
+
j.variableDeclarator(j.identifier(
|
|
164
|
+
// Use alias from destructuring (if present) to retain references:
|
|
165
|
+
// const { geo: geoAlias } = req; -> const geoAlias = geolocation(req);
|
|
166
|
+
// This prevents errors from undeclared variables.
|
|
167
|
+
geoProperty.type === 'ObjectProperty' &&
|
|
168
|
+
geoProperty.value.type === 'Identifier'
|
|
169
|
+
? geoProperty.value.name
|
|
170
|
+
: GEO), geoCall),
|
|
171
|
+
]);
|
|
172
|
+
path.node.id.properties = otherProperties;
|
|
173
|
+
path.parent.insertAfter(geoDeclaration);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
ipDestructuring.forEach((path) => {
|
|
177
|
+
if (path.node.id.type === 'ObjectPattern') {
|
|
178
|
+
const properties = path.node.id.properties;
|
|
179
|
+
const ipProperty = properties.find((prop) => prop.type === 'ObjectProperty' &&
|
|
180
|
+
prop.key.type === 'Identifier' &&
|
|
181
|
+
prop.key.name === IP);
|
|
182
|
+
const otherProperties = properties.filter((prop) => prop !== ipProperty);
|
|
183
|
+
const ipDeclaration = j.variableDeclaration('const', [
|
|
184
|
+
j.variableDeclarator(j.identifier(
|
|
185
|
+
// Use alias from destructuring (if present) to retain references:
|
|
186
|
+
// const { ip: ipAlias } = req; -> const ipAlias = ipAddress(req);
|
|
187
|
+
// This prevents errors from undeclared variables.
|
|
188
|
+
ipProperty.type === 'ObjectProperty' &&
|
|
189
|
+
ipProperty.value.type === 'Identifier'
|
|
190
|
+
? ipProperty.value.name
|
|
191
|
+
: IP), ipCall),
|
|
192
|
+
]);
|
|
193
|
+
path.node.id.properties = otherProperties;
|
|
194
|
+
path.parent.insertAfter(ipDeclaration);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
needImportGeolocation =
|
|
198
|
+
needImportGeolocation ||
|
|
199
|
+
geoAccesses.length > 0 ||
|
|
200
|
+
geoDestructuring.length > 0;
|
|
201
|
+
needImportIpAddress =
|
|
202
|
+
needImportIpAddress || ipAccesses.length > 0 || ipDestructuring.length > 0;
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
needImportGeolocation,
|
|
206
|
+
needImportIpAddress,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Replaces the types of `NextRequest["geo"]` and `NextRequest["ip"]` with
|
|
211
|
+
* corresponding types from `@vercel/functions`.
|
|
212
|
+
*/
|
|
213
|
+
function replaceGeoIpTypes(j, root, geoTypeIdentifier) {
|
|
214
|
+
let needImportGeoType = false;
|
|
215
|
+
let hasChangedIpType = false;
|
|
216
|
+
// get the type of NextRequest that has accessed for ip and geo
|
|
217
|
+
// NextRequest['geo'], NextRequest['ip']
|
|
218
|
+
const nextReqGeoType = root.find(j.TSIndexedAccessType, (tsIndexedAccessType) => {
|
|
219
|
+
return (tsIndexedAccessType.objectType.type === 'TSTypeReference' &&
|
|
220
|
+
tsIndexedAccessType.objectType.typeName.type === 'Identifier' &&
|
|
221
|
+
tsIndexedAccessType.objectType.typeName.name === 'NextRequest' &&
|
|
222
|
+
tsIndexedAccessType.indexType.type === 'TSLiteralType' &&
|
|
223
|
+
tsIndexedAccessType.indexType.literal.type === 'StringLiteral' &&
|
|
224
|
+
tsIndexedAccessType.indexType.literal.value === GEO);
|
|
225
|
+
});
|
|
226
|
+
const nextReqIpType = root.find(j.TSIndexedAccessType, (tsIndexedAccessType) => {
|
|
227
|
+
return (tsIndexedAccessType.objectType.type === 'TSTypeReference' &&
|
|
228
|
+
tsIndexedAccessType.objectType.typeName.type === 'Identifier' &&
|
|
229
|
+
tsIndexedAccessType.objectType.typeName.name === 'NextRequest' &&
|
|
230
|
+
tsIndexedAccessType.indexType.type === 'TSLiteralType' &&
|
|
231
|
+
tsIndexedAccessType.indexType.literal.type === 'StringLiteral' &&
|
|
232
|
+
tsIndexedAccessType.indexType.literal.value === IP);
|
|
233
|
+
});
|
|
234
|
+
if (nextReqGeoType.length > 0) {
|
|
235
|
+
needImportGeoType = true;
|
|
236
|
+
// replace with type Geo
|
|
237
|
+
nextReqGeoType.replaceWith(j.identifier(geoTypeIdentifier));
|
|
238
|
+
}
|
|
239
|
+
if (nextReqIpType.length > 0) {
|
|
240
|
+
hasChangedIpType = true;
|
|
241
|
+
// replace with type string | undefined
|
|
242
|
+
nextReqIpType.replaceWith(j.tsUnionType([j.tsStringKeyword(), j.tsUndefinedKeyword()]));
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
needImportGeoType,
|
|
246
|
+
hasChangedIpType,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Inserts import declarations from `"@vercel/functions"`.
|
|
251
|
+
*
|
|
252
|
+
* For the `Geo` type that needs to be imported to replace `NextRequest["geo"]`,
|
|
253
|
+
* if it is the only import needed, we import it as a type.
|
|
254
|
+
*
|
|
255
|
+
* ```ts
|
|
256
|
+
* import type { Geo } from "@vercel/functions";
|
|
257
|
+
* ```
|
|
258
|
+
*
|
|
259
|
+
* Otherwise, we import it as an inline type import.
|
|
260
|
+
*
|
|
261
|
+
* ```ts
|
|
262
|
+
* import { type Geo, ... } from "@vercel/functions";
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
function insertImportDeclarations(j, root, vercelFuncImports, needImportGeolocation, needImportIpAddress, needImportGeoType, geoIdentifier, ipIdentifier, geoTypeIdentifier) {
|
|
266
|
+
// No need inserting import.
|
|
267
|
+
if (!needImportGeolocation && !needImportIpAddress && !needImportGeoType) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
// As we run this only when `NextRequest` is imported, there should be
|
|
271
|
+
// a "next/server" import.
|
|
272
|
+
const firstNextServerImport = root
|
|
273
|
+
.find(j.ImportDeclaration, { source: { value: 'next/server' } })
|
|
274
|
+
.at(0);
|
|
275
|
+
const firstVercelFuncImport = vercelFuncImports.at(0);
|
|
276
|
+
const hasVercelFuncImport = firstVercelFuncImport.length > 0;
|
|
277
|
+
// If there's no "@vercel/functions" import, and we only need to import
|
|
278
|
+
// `Geo` type, we create a type import to avoid side effect with
|
|
279
|
+
// `verbatimModuleSyntax`.
|
|
280
|
+
// x-ref: https://typescript-eslint.io/rules/no-import-type-side-effects
|
|
281
|
+
if (!hasVercelFuncImport &&
|
|
282
|
+
!needImportGeolocation &&
|
|
283
|
+
!needImportIpAddress &&
|
|
284
|
+
needImportGeoType) {
|
|
285
|
+
const geoTypeImportDeclaration = j.importDeclaration([
|
|
286
|
+
needImportGeoType
|
|
287
|
+
? j.importSpecifier(j.identifier(GEO_TYPE), j.identifier(geoTypeIdentifier))
|
|
288
|
+
: null,
|
|
289
|
+
].filter(Boolean), j.literal('@vercel/functions'),
|
|
290
|
+
// import type { Geo } ...
|
|
291
|
+
'type');
|
|
292
|
+
firstNextServerImport.insertAfter(geoTypeImportDeclaration);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const importDeclaration = j.importDeclaration([
|
|
296
|
+
// If there was a duplicate identifier, we add an
|
|
297
|
+
// incremental number suffix to it and we use alias:
|
|
298
|
+
// `import { geolocation as geolocation1 } from ...`
|
|
299
|
+
needImportGeolocation
|
|
300
|
+
? j.importSpecifier(j.identifier(GEOLOCATION), j.identifier(geoIdentifier))
|
|
301
|
+
: null,
|
|
302
|
+
needImportIpAddress
|
|
303
|
+
? j.importSpecifier(j.identifier(IP_ADDRESS), j.identifier(ipIdentifier))
|
|
304
|
+
: null,
|
|
305
|
+
needImportGeoType
|
|
306
|
+
? j.importSpecifier(j.identifier(GEO_TYPE), j.identifier(geoTypeIdentifier))
|
|
307
|
+
: null,
|
|
308
|
+
].filter(Boolean), j.literal('@vercel/functions'));
|
|
309
|
+
if (hasVercelFuncImport) {
|
|
310
|
+
firstVercelFuncImport
|
|
311
|
+
.get()
|
|
312
|
+
.node.specifiers.push(...importDeclaration.specifiers);
|
|
313
|
+
if (needImportGeoType) {
|
|
314
|
+
const targetGeo = firstVercelFuncImport
|
|
315
|
+
.get()
|
|
316
|
+
.node.specifiers.find((specifier) => specifier.imported.name === GEO_TYPE);
|
|
317
|
+
if (targetGeo) {
|
|
318
|
+
targetGeo.importKind = 'type';
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
if (needImportGeoType) {
|
|
324
|
+
const targetGeo = importDeclaration.specifiers.find((specifier) => specifier.type === 'ImportSpecifier' &&
|
|
325
|
+
specifier.imported.name === GEO_TYPE);
|
|
326
|
+
if (targetGeo) {
|
|
327
|
+
// @ts-expect-error -- Missing types in jscodeshift.
|
|
328
|
+
targetGeo.importKind = 'type';
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
firstNextServerImport.insertAfter(importDeclaration);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
//# sourceMappingURL=next-request-geo-ip.js.map
|