@next/codemod 16.0.0-canary.3 → 16.0.0-canary.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next/codemod",
3
- "version": "16.0.0-canary.3",
3
+ "version": "16.0.0-canary.5",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -4,20 +4,257 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.default = transformer;
7
- const path_1 = __importDefault(require("path"));
8
7
  const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = require("path");
9
+ const utils_1 = require("./lib/utils");
9
10
  const parser_1 = require("../lib/parser");
11
+ // Middleware config properties that need to be renamed to proxy equivalents
12
+ const CONFIG_PROPERTY_MAP = {
13
+ middlewarePrefetch: 'proxyPrefetch',
14
+ middlewareClientMaxBodySize: 'proxyClientMaxBodySize',
15
+ externalMiddlewareRewritesResolve: 'externalProxyRewritesResolve',
16
+ skipMiddlewareUrlNormalize: 'skipProxyUrlNormalize',
17
+ };
18
+ // Type imports from 'next/server' that need to be transformed
19
+ const MIDDLEWARE_TYPE_IMPORT_MAP = {
20
+ NextMiddleware: 'NextProxy',
21
+ MiddlewareConfig: 'ProxyConfig',
22
+ };
10
23
  function transformer(file) {
11
- if (!/(^|[/\\])middleware\.|[/\\]src[/\\]middleware\./.test(file.path) &&
12
- // fixtures have unique basenames in test
13
- process.env.NODE_ENV !== 'test') {
14
- return file.source;
15
- }
16
24
  const j = (0, parser_1.createParserFromPath)(file.path);
17
25
  const root = j(file.source);
18
26
  if (!root.length) {
19
27
  return file.source;
20
28
  }
29
+ const isNextConfig = (0, utils_1.isNextConfigFile)(file) ||
30
+ (process.env.NODE_ENV === 'test' && /next-config-/.test(file.path));
31
+ const isMiddlewareFile = /(^|[/\\])middleware\.|[/\\]src[/\\]middleware\./.test(file.path) ||
32
+ (process.env.NODE_ENV === 'test' && !isNextConfig);
33
+ const hasMiddlewareTypeImports = checkForNextServerTypeImports(root, j);
34
+ // In test mode, process all files. Otherwise, only process relevant files
35
+ if (process.env.NODE_ENV !== 'test') {
36
+ if (!isMiddlewareFile && !isNextConfig && !hasMiddlewareTypeImports) {
37
+ return file.source;
38
+ }
39
+ }
40
+ let hasChanges = false;
41
+ if (hasMiddlewareTypeImports) {
42
+ const typeImportChanges = transformMiddlewareTypeImports(root, j);
43
+ hasChanges = hasChanges || typeImportChanges;
44
+ }
45
+ if (isMiddlewareFile) {
46
+ const middlewareChanges = transformMiddlewareFunction(root, j);
47
+ hasChanges = hasChanges || middlewareChanges.hasChanges;
48
+ }
49
+ if (isNextConfig) {
50
+ const { hasConfigChanges } = transformNextConfig(root, j);
51
+ hasChanges = hasChanges || hasConfigChanges;
52
+ }
53
+ if (!hasChanges) {
54
+ return file.source;
55
+ }
56
+ const source = root.toSource();
57
+ // Need to write proxy file and unlink the original middleware file.
58
+ if (isMiddlewareFile) {
59
+ return handleMiddlewareFileRename(file, source);
60
+ }
61
+ return source;
62
+ }
63
+ function checkForNextServerTypeImports(root, j) {
64
+ return (root
65
+ .find(j.ImportDeclaration, {
66
+ source: { value: 'next/server' },
67
+ })
68
+ .find(j.ImportSpecifier)
69
+ .filter((path) => MIDDLEWARE_TYPE_IMPORT_MAP[path.node.imported.name]).length > 0);
70
+ }
71
+ function transformMiddlewareTypeImports(root, j) {
72
+ let hasChanges = false;
73
+ // Transform type imports from 'next/server'
74
+ root
75
+ .find(j.ImportDeclaration, {
76
+ source: { value: 'next/server' },
77
+ })
78
+ .forEach((importPath) => {
79
+ const specifiers = importPath.node.specifiers;
80
+ if (!specifiers)
81
+ return;
82
+ specifiers.forEach((specifier) => {
83
+ if (j.ImportSpecifier.check(specifier) &&
84
+ specifier.imported &&
85
+ MIDDLEWARE_TYPE_IMPORT_MAP[specifier.imported.name]) {
86
+ const oldImportName = specifier.imported.name;
87
+ const newImportName = MIDDLEWARE_TYPE_IMPORT_MAP[oldImportName];
88
+ // Update the local name if it matches the original imported name
89
+ if (specifier.local && specifier.local.name === oldImportName) {
90
+ specifier.local.name = newImportName;
91
+ }
92
+ // Transform the import name
93
+ specifier.imported.name = newImportName;
94
+ hasChanges = true;
95
+ }
96
+ });
97
+ });
98
+ // Also transform any type annotations using the old types
99
+ Object.keys(MIDDLEWARE_TYPE_IMPORT_MAP).forEach((oldType) => {
100
+ root
101
+ .find(j.TSTypeReference)
102
+ .filter((path) => {
103
+ return (path.node.typeName &&
104
+ path.node.typeName.type === 'Identifier' &&
105
+ path.node.typeName.name === oldType);
106
+ })
107
+ .forEach((path) => {
108
+ path.node.typeName.name = MIDDLEWARE_TYPE_IMPORT_MAP[oldType];
109
+ hasChanges = true;
110
+ });
111
+ });
112
+ return hasChanges;
113
+ }
114
+ function transformNextConfig(root, j) {
115
+ let hasConfigChanges = false;
116
+ // Collect config-related object expressions instead of processing all
117
+ const configObjects = findNextConfigObjects(root, j);
118
+ configObjects.forEach((objPath) => {
119
+ const result = processConfigObject(objPath.value);
120
+ hasConfigChanges = hasConfigChanges || result.hasChanges;
121
+ });
122
+ // Process function configurations that are likely to be Next.js config
123
+ const configFunctions = findNextConfigFunctions(root, j);
124
+ configFunctions.forEach((path) => {
125
+ const result = processFunctionConfig(path, j);
126
+ hasConfigChanges = hasConfigChanges || result.hasChanges;
127
+ });
128
+ // Process arrow function configurations that are likely to be Next.js config
129
+ const configArrowFunctions = findNextConfigArrowFunctions(root, j);
130
+ configArrowFunctions.forEach((path) => {
131
+ const result = processArrowFunctionConfig(path, j);
132
+ hasConfigChanges = hasConfigChanges || result.hasChanges;
133
+ });
134
+ // Process direct property assignments: config.experimental.middlewarePrefetch = value
135
+ Object.keys(CONFIG_PROPERTY_MAP).forEach((oldProp) => {
136
+ const newProp = CONFIG_PROPERTY_MAP[oldProp];
137
+ // Handle experimental.* properties
138
+ if (oldProp.startsWith('middleware') &&
139
+ oldProp !== 'skipMiddlewareUrlNormalize') {
140
+ root
141
+ .find(j.AssignmentExpression, {
142
+ left: {
143
+ type: 'MemberExpression',
144
+ object: {
145
+ type: 'MemberExpression',
146
+ property: { name: 'experimental' },
147
+ },
148
+ property: { name: oldProp },
149
+ },
150
+ })
151
+ .forEach((path) => {
152
+ path.node.left.property.name = newProp;
153
+ hasConfigChanges = true;
154
+ });
155
+ }
156
+ else {
157
+ // Handle top-level properties like skipMiddlewareUrlNormalize
158
+ root
159
+ .find(j.AssignmentExpression, {
160
+ left: {
161
+ type: 'MemberExpression',
162
+ property: { name: oldProp },
163
+ },
164
+ })
165
+ .forEach((path) => {
166
+ path.node.left.property.name = newProp;
167
+ hasConfigChanges = true;
168
+ });
169
+ }
170
+ });
171
+ return { hasConfigChanges };
172
+ }
173
+ function processConfigObject(configObj) {
174
+ let hasChanges = false;
175
+ // Check for experimental property
176
+ const experimentalProp = configObj.properties.find((prop) => isStaticProperty(prop) &&
177
+ prop.key &&
178
+ prop.key.type === 'Identifier' &&
179
+ prop.key.name === 'experimental');
180
+ if (experimentalProp && isStaticProperty(experimentalProp)) {
181
+ const experimentalObj = experimentalProp.value;
182
+ if (experimentalObj.type === 'ObjectExpression') {
183
+ // Transform properties in experimental object
184
+ experimentalObj.properties.forEach((prop) => {
185
+ if (isStaticProperty(prop) &&
186
+ prop.key &&
187
+ prop.key.type === 'Identifier' &&
188
+ CONFIG_PROPERTY_MAP[prop.key.name] &&
189
+ prop.key.name !== 'skipMiddlewareUrlNormalize' // This is top-level
190
+ ) {
191
+ prop.key.name = CONFIG_PROPERTY_MAP[prop.key.name];
192
+ hasChanges = true;
193
+ }
194
+ });
195
+ }
196
+ }
197
+ // Transform top-level properties
198
+ configObj.properties.forEach((prop) => {
199
+ if (isStaticProperty(prop) &&
200
+ prop.key &&
201
+ prop.key.type === 'Identifier' &&
202
+ prop.key.name === 'skipMiddlewareUrlNormalize') {
203
+ prop.key.name = CONFIG_PROPERTY_MAP[prop.key.name];
204
+ hasChanges = true;
205
+ }
206
+ });
207
+ // Also transform any top-level middleware properties (for spread scenarios)
208
+ configObj.properties.forEach((prop) => {
209
+ if (isStaticProperty(prop) &&
210
+ prop.key &&
211
+ prop.key.type === 'Identifier' &&
212
+ CONFIG_PROPERTY_MAP[prop.key.name] &&
213
+ prop.key.name !== 'skipMiddlewareUrlNormalize' // Already handled above
214
+ ) {
215
+ prop.key.name = CONFIG_PROPERTY_MAP[prop.key.name];
216
+ hasChanges = true;
217
+ }
218
+ });
219
+ return { hasChanges };
220
+ }
221
+ function processFunctionConfig(path, j) {
222
+ let hasChanges = false;
223
+ // Look for return statements with object expressions
224
+ j(path)
225
+ .find(j.ReturnStatement)
226
+ .forEach((returnPath) => {
227
+ if (returnPath.node.argument &&
228
+ returnPath.node.argument.type === 'ObjectExpression') {
229
+ const result = processConfigObject(returnPath.node.argument);
230
+ hasChanges = hasChanges || result.hasChanges;
231
+ }
232
+ });
233
+ return { hasChanges };
234
+ }
235
+ function processArrowFunctionConfig(path, j) {
236
+ let hasChanges = false;
237
+ const body = path.node.body;
238
+ // Handle: () => ({ ... })
239
+ if (body && body.type === 'ObjectExpression') {
240
+ const result = processConfigObject(body);
241
+ hasChanges = hasChanges || result.hasChanges;
242
+ }
243
+ // Handle: () => { return { ... } }
244
+ if (body && body.type === 'BlockStatement') {
245
+ j(path)
246
+ .find(j.ReturnStatement)
247
+ .forEach((returnPath) => {
248
+ if (returnPath.node.argument &&
249
+ returnPath.node.argument.type === 'ObjectExpression') {
250
+ const result = processConfigObject(returnPath.node.argument);
251
+ hasChanges = hasChanges || result.hasChanges;
252
+ }
253
+ });
254
+ }
255
+ return { hasChanges };
256
+ }
257
+ function transformMiddlewareFunction(root, j) {
21
258
  const proxyIdentifier = generateUniqueIdentifier(root, j, 'proxy');
22
259
  const needsAlias = proxyIdentifier !== 'proxy';
23
260
  let hasChanges = false;
@@ -125,9 +362,6 @@ function transformer(file) {
125
362
  astPath.node.name = proxyIdentifier;
126
363
  });
127
364
  }
128
- if (!hasChanges) {
129
- return file.source;
130
- }
131
365
  // If we used a unique identifier AND we exported `as proxy`, add an export alias
132
366
  // This handles cases where the export was part of the declaration itself:
133
367
  // export function middleware() {} -> export function _proxy1() {} (needs alias)
@@ -158,23 +392,30 @@ function transformer(file) {
158
392
  }
159
393
  }
160
394
  }
161
- const source = root.toSource();
395
+ return { hasChanges };
396
+ }
397
+ function handleMiddlewareFileRename(file, source) {
162
398
  // We will not modify the original file in real world,
163
399
  // so return the source here for testing.
164
400
  if (process.env.NODE_ENV === 'test') {
165
401
  return source;
166
402
  }
167
- const { dir, ext } = path_1.default.parse(file.path);
168
- const newFilePath = path_1.default.join(dir, 'proxy' + ext);
403
+ const { dir, ext } = (0, path_1.parse)(file.path);
404
+ const newFilePath = (0, path_1.join)(dir, 'proxy' + ext);
169
405
  try {
170
406
  fs_1.default.writeFileSync(newFilePath, source);
171
407
  fs_1.default.unlinkSync(file.path);
408
+ // Return empty string to indicate successful file replacement.
409
+ return '';
172
410
  }
173
411
  catch (cause) {
174
412
  console.error(`Failed to write "${newFilePath}" and delete "${file.path}".\n${JSON.stringify({ cause })}`);
175
413
  return file.source;
176
414
  }
177
415
  }
416
+ function isStaticProperty(prop) {
417
+ return prop.type === 'Property' || prop.type === 'ObjectProperty';
418
+ }
178
419
  function generateUniqueIdentifier(root, j, baseName) {
179
420
  // First check if baseName itself is available
180
421
  if (!hasIdentifierInScope(root, j, baseName)) {
@@ -206,4 +447,159 @@ function hasIdentifierInScope(root, j, name) {
206
447
  astPath.value.local.name === name).length > 0;
207
448
  return hasVariableDeclaration || hasFunctionDeclaration || hasImportSpecifier;
208
449
  }
450
+ function findNextConfigObjects(root, j) {
451
+ const configObjects = [];
452
+ // Find identifiers that are exported as default or assigned to module.exports
453
+ const exportedNames = new Set();
454
+ // Handle: export default nextConfig or export default wrappedFunction(nextConfig)
455
+ root.find(j.ExportDefaultDeclaration).forEach((path) => {
456
+ if (j.Identifier.check(path.node.declaration)) {
457
+ exportedNames.add(path.node.declaration.name);
458
+ }
459
+ else if (j.ObjectExpression.check(path.node.declaration)) {
460
+ // Direct object export: export default { ... }
461
+ configObjects.push(path.get('declaration'));
462
+ }
463
+ else if (j.CallExpression.check(path.node.declaration)) {
464
+ // Handle wrapped exports: export default wrapper(config)
465
+ extractObjectsFromCallExpression(path.node.declaration, configObjects, exportedNames, j);
466
+ }
467
+ });
468
+ // Handle: module.exports = nextConfig or module.exports = wrappedFunction(nextConfig)
469
+ root
470
+ .find(j.AssignmentExpression, {
471
+ left: {
472
+ type: 'MemberExpression',
473
+ object: { name: 'module' },
474
+ property: { name: 'exports' },
475
+ },
476
+ })
477
+ .forEach((path) => {
478
+ if (j.Identifier.check(path.node.right)) {
479
+ exportedNames.add(path.node.right.name);
480
+ }
481
+ else if (j.ObjectExpression.check(path.node.right)) {
482
+ // Direct object assignment: module.exports = { ... }
483
+ configObjects.push(path.get('right'));
484
+ }
485
+ else if (j.CallExpression.check(path.node.right)) {
486
+ // Handle wrapped assignments: module.exports = wrapper(config)
487
+ extractObjectsFromCallExpression(path.node.right, configObjects, exportedNames, j);
488
+ }
489
+ });
490
+ // Find variable declarations for exported names
491
+ exportedNames.forEach((name) => {
492
+ root
493
+ .find(j.VariableDeclarator, { id: { name } })
494
+ .forEach((path) => {
495
+ if (j.ObjectExpression.check(path.node.init)) {
496
+ configObjects.push(path.get('init'));
497
+ }
498
+ });
499
+ });
500
+ return configObjects;
501
+ }
502
+ function findNextConfigFunctions(root, j) {
503
+ const configFunctions = [];
504
+ const exportedNames = new Set();
505
+ // Handle: export default function or export default functionName
506
+ root.find(j.ExportDefaultDeclaration).forEach((path) => {
507
+ if (j.FunctionDeclaration.check(path.node.declaration)) {
508
+ // export default function configFunction() { ... }
509
+ configFunctions.push(path.get('declaration'));
510
+ }
511
+ else if (j.Identifier.check(path.node.declaration)) {
512
+ exportedNames.add(path.node.declaration.name);
513
+ }
514
+ });
515
+ // Handle: module.exports = function
516
+ root
517
+ .find(j.AssignmentExpression, {
518
+ left: {
519
+ type: 'MemberExpression',
520
+ object: { name: 'module' },
521
+ property: { name: 'exports' },
522
+ },
523
+ })
524
+ .forEach((path) => {
525
+ if (j.FunctionExpression.check(path.node.right)) {
526
+ // module.exports = function() { ... }
527
+ configFunctions.push(path.get('right'));
528
+ }
529
+ else if (j.Identifier.check(path.node.right)) {
530
+ exportedNames.add(path.node.right.name);
531
+ }
532
+ });
533
+ // Find function declarations for exported names
534
+ exportedNames.forEach((name) => {
535
+ root
536
+ .find(j.FunctionDeclaration, { id: { name } })
537
+ .forEach((path) => {
538
+ configFunctions.push(path);
539
+ });
540
+ });
541
+ return configFunctions;
542
+ }
543
+ function findNextConfigArrowFunctions(root, j) {
544
+ const configArrowFunctions = [];
545
+ const exportedNames = new Set();
546
+ // Handle: export default arrowFunction
547
+ root.find(j.ExportDefaultDeclaration).forEach((path) => {
548
+ if (j.ArrowFunctionExpression.check(path.node.declaration)) {
549
+ // export default () => { ... }
550
+ configArrowFunctions.push(path.get('declaration'));
551
+ }
552
+ else if (j.Identifier.check(path.node.declaration)) {
553
+ exportedNames.add(path.node.declaration.name);
554
+ }
555
+ });
556
+ // Handle: module.exports = arrowFunction
557
+ root
558
+ .find(j.AssignmentExpression, {
559
+ left: {
560
+ type: 'MemberExpression',
561
+ object: { name: 'module' },
562
+ property: { name: 'exports' },
563
+ },
564
+ })
565
+ .forEach((path) => {
566
+ if (j.ArrowFunctionExpression.check(path.node.right)) {
567
+ // module.exports = () => { ... }
568
+ configArrowFunctions.push(path.get('right'));
569
+ }
570
+ else if (j.Identifier.check(path.node.right)) {
571
+ exportedNames.add(path.node.right.name);
572
+ }
573
+ });
574
+ // Find variable declarations with arrow functions for exported names
575
+ exportedNames.forEach((name) => {
576
+ root
577
+ .find(j.VariableDeclarator, { id: { name } })
578
+ .forEach((path) => {
579
+ if (j.ArrowFunctionExpression.check(path.node.init)) {
580
+ configArrowFunctions.push(path.get('init'));
581
+ }
582
+ });
583
+ });
584
+ return configArrowFunctions;
585
+ }
586
+ function extractObjectsFromCallExpression(callExpr, configObjects, exportedNames, j) {
587
+ // Recursively extract arguments from call expressions
588
+ // E.g., wrapper(anotherWrapper(config)) or wrapper(config)
589
+ if (callExpr.arguments) {
590
+ callExpr.arguments.forEach((arg) => {
591
+ if (j.Identifier.check(arg)) {
592
+ exportedNames.add(arg.name);
593
+ }
594
+ else if (j.ObjectExpression.check(arg)) {
595
+ // This would be unusual but handle direct object arguments
596
+ // We don't have the path here, so we'll skip this case
597
+ // It would be handled by the direct export case anyway
598
+ }
599
+ else if (j.CallExpression.check(arg)) {
600
+ extractObjectsFromCallExpression(arg, configObjects, exportedNames, j);
601
+ }
602
+ });
603
+ }
604
+ }
209
605
  //# sourceMappingURL=middleware-to-proxy.js.map